├── .gitattributes ├── README.md ├── exercises ├── Exercise1-SimpleCommit.md ├── Exercise10-Hooks.md ├── Exercise11-GitHubAPI.md ├── Exercise2-StagingAndStashing.md ├── Exercise3-References.md ├── Exercise4-MergingAndReReRe.md ├── Exercise5-HistoryAndDiffs.md ├── Exercise6-FixingMistakes.md ├── Exercise7-RebaseAndAmend.md ├── Exercise8-ForksAndRemoteRepos.md └── Exercise9-AdvancedTools.md ├── images └── git-in-depth.png └── presentation └── slides.pdf /.gitattributes: -------------------------------------------------------------------------------- 1 | *.png filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## [Advanced Git In-Depth](https://frontendmasters.com/courses/git-in-depth/) 2 | 3 | The accompanying exercises for [Nina Zakharenko's](https://twitter.com/nnja) [Git In-Depth Video Workshop](https://frontendmasters.com/courses/git-in-depth/) on Frontend Masters. 4 | 5 | ![](images/git-in-depth.png) 6 | 7 | [Watch the Course here](https://frontendmasters.com/courses/git-in-depth/). 8 | 9 | ## Resources 10 | 11 | All of the resources you need for class are located in this repository. 12 | 13 | ### Slides 14 | The PDF of the slides is in the `presentation` folder. 15 | 16 | ### Exercises 17 | are in the `exercises` folder. The exercises will instruct you to clone an additional repository that is set up for completing the included exercises. 18 | 19 | 20 | ## Description 21 | 22 | By coding along with us in this workshop you’ll: 23 | 24 | - Learn how git represents commits and branches internally to gain deep insights into how git works under the hood… 25 | - Discover the power of rebasing in git, along with how to avoid common pitfalls. Learn to tell when it’s appropriate to re-write history… 26 | - Discover how git hooks can make your life easier by running analysis on your code before you commit in order to prevent common errors from making their way into your codebase… 27 | - Discover lesser-known features of git that will save you hours of time when resolving merge conflicts and track down bugs… 28 | - Learn how to harness the power of the GitHub API to fetch information about your repositories. 29 | 30 | ## Author and Class Information 31 | 32 | The course content and exercises were written by Nina Zakharenko. 33 | 34 | Find her on: 35 | - [Twitter @nnja](https://twitter.com/nnja) 36 | - [LinkedIn](https://www.linkedin.com/in/ninaz/) 37 | - [GitHub](https://github.com/nnja) 38 | 39 | ## Video Course 40 | 41 | Front End Masters [Video screencast](http://frontendmasters.com/workshops/git-indepth/) 42 | 43 | -------------------------------------------------------------------------------- /exercises/Exercise1-SimpleCommit.md: -------------------------------------------------------------------------------- 1 | # Advanced Git 2 | ## Exercise One - Under The Hood of a Simple Commit 3 | 4 | ### Overview 5 | In this exercise, we'll create a simple commit, and then peek under the hood at the objects stored in our `.git` folder to gain some insight into how things work. 6 | 7 | ### Prerequisite 8 | If you have a Mac with `brew` set up, install `tree`. This makes it easy to visualize the contents of your `.git` folder. 9 | 10 | ### Exercise 11 | 1. Create a new folder and initialize it as a git repo 12 | 2. Create a file, stage it, and commit it to your new repo 13 | 3. Look at your `.git` folder, using `tree` if you have it 14 | 4. Inspect the objects in your `.git/objects` folder using `git cat-file`. See if you can find the tree, blob, and commit objects for your recent commit. 15 | 5. Look at your `.git/HEAD` and `.git/refs/heads/master` files and see if you can figure out where these references are pointing to. 16 | 17 | ## Solutions 18 | 19 | ### Step 1 - Initialize the Repo 20 | Create a new sample project folder. Run `git status` to see that it is not yet a git repository. Use `git init` to initialize it as a repository. 21 | 22 | ``` 23 | $> mkdir -p ~/projects/sample 24 | 25 | $> cd ~/projects/sample 26 | 27 | $> git status 28 | fatal: Not a git repository (or any of the parent directories): .git 29 | 30 | $> git init 31 | Initialized empty Git repository in /Users/nnja/projects/sample/.git/ 32 | ``` 33 | 34 | ### Step 2 - First Commit 35 | Create a new document, stage it for a commit, then commit it to your repository. 36 | 37 | ``` 38 | $> echo 'Hello World!' > hello.txt 39 | 40 | $> git add hello.txt 41 | 42 | $> git commit -m "Initial commit" 43 | [master (root-commit) aceb9e8] Initial commit 44 | 1 file changed, 1 insertion(+) 45 | create mode 100644 hello.txt 46 | ``` 47 | 48 | ### Step 3 - View the .git Folder 49 | Using `tree`, look in your `.git/objects` folder, you should now see three objects, represented by long SHA1 hashes. These represent the tree, blob, and commit that we created in the last step. 50 | 51 | ``` 52 | $> tree .git 53 | 54 | .git 55 | ├── COMMIT_EDITMSG 56 | ├── HEAD 57 | ├── config 58 | ├── description 59 | ├── index 60 | ├── info 61 | │   └── exclude 62 | ├── logs 63 | │   ├── HEAD 64 | │   └── refs 65 | │   └── heads 66 | │   └── master 67 | ├── objects 68 | │   ├── 43 69 | │   │   └── 388fee19744e8893467331d7853a6475a227b8 70 | │   ├── 58 71 | │   │   └── 1caa0fe56cf01dc028cc0b089d364993e046b6 72 | │   ├── 98 73 | │   │   └── 0a0d5f19a64b4b30a87d4206aade58726b60e3 74 | │   ├── info 75 | │   └── pack 76 | └── refs 77 | ├── heads 78 | │   └── master 79 | └── tags 80 | ``` 81 | 82 | ### Step 4 - Inspect the Objects: 83 | Note: The SHA1 hash for your commit will be different than the one displayed here. The SHA1 hash for your `blob` and `tree` will be the same as mine, as long as the content is the same. 84 | 85 | One of the objects should be a tree object. The tree contains the filename `hello.txt` and a pointer to the blob. 86 | 87 | ``` 88 | $> git cat-file -t 581caa 89 | tree 90 | 91 | $> git cat-file -p 581caa 92 | 100644 blob 980a0d5f19a64b4b30a87d4206aade58726b60e3 hello.txt 93 | ``` 94 | 95 | The blob object, pointed to by the tree, contains the contents of the file `hello.txt` 96 | 97 | ``` 98 | $> git cat-file -t 980a0d5 99 | blob 100 | 101 | $> git cat-file -p 980a0d5 102 | Hello World! 103 | ``` 104 | 105 | The commit object contains a pointer to the tree, along with metadata for the commit, such as the author and commit message. 106 | 107 | ``` 108 | $> git cat-file -t 43388f 109 | commit 110 | 111 | $> git cat-file -p 43388f 112 | tree 581caa0fe56cf01dc028cc0b089d364993e046b6 113 | author Nina Zakharenko 1507168309 -0700 114 | committer Nina Zakharenko 1507168309 -0700 115 | 116 | Initial commit 117 | ``` 118 | 119 | Because this is our very first commit, it doesn't have a parent. The next commit we make will point to our initial commit as the parent. 120 | 121 | ### Step 5 - Look at refs 122 | 123 | Let's look under the hood at our `HEAD` variable. `HEAD` is just git's pointer to "where you are now," usually referring to the current branch. More on this later. We can see that right now, it points to our current branch - `master` 124 | 125 | Now, if we look at our `master` reference, we can see that it points to the latest commit. 126 | 127 | ``` 128 | $> cat .git/HEAD 129 | ref: refs/heads/master 130 | 131 | $> cat .git/refs/heads/master 132 | 43388fee19744e8893467331d7853a6475a227b8 133 | ``` 134 | `43388f...` is the hash of the commit we saw in the last step. You can confirm this by running `git log` 135 | 136 | ``` 137 | $> git log --oneline 138 | 43388f Initial commit 139 | ``` 140 | 141 | Git stores references in the `.git/refs/heads/` directory, and the `HEAD` pointer in `.git/HEAD` 142 | 143 | We can verify this by creating a new branch. 144 | 145 | ``` 146 | $> git branch new_branch 147 | ``` 148 | 149 | The `git branch` command will create a new branch without switching to it. 150 | 151 | Now, if we look in `.git/refs`, we'll see two branches. The `master` branch, which is created by default, and `new_branch`. 152 | 153 | ``` 154 | $> tree .git/refs 155 | .git/refs 156 | ├── heads 157 | │   ├── master 158 | │   └── new_branch 159 | └── tags 160 | 161 | ``` 162 | 163 | #### End of Exercise One -------------------------------------------------------------------------------- /exercises/Exercise10-Hooks.md: -------------------------------------------------------------------------------- 1 | # Advanced Git 2 | ## Exercise Ten - Hooks 3 | 4 | ### Overview 5 | In this exercise, we'll set up a pre-commit hook and see how this powerful tool might be used to keep low-quality code from getting into your repo. 6 | 7 | ### Prerequisite 8 | You should have the [`advanced-git-exercises`](https://github.com/nnja/advanced-git-exercises) repository cloned locally. Checkout the `exercise10` branch to begin: 9 | 10 | ``` 11 | $> git checkout exercise10 12 | Switched to branch 'exercise10' 13 | ``` 14 | 15 | Note: This pre-commit hook script will only work in a Bash environment. Windows users can follow along but the script my not work as expected. 16 | 17 | ### Exercise 18 | 1. Copy the `pre-commit` script into your git hooks folder and make it executable. Try committing a shell (.sh) script to your repo with no shebang (#!) line at the top - your commit should fail. Try committing a script with a shebang line - your commit should succeed. 19 | 20 | ## Solution 21 | 22 | ### Step 1 - Set up a Pre-Commit Hook 23 | Git has the ability to call arbitrary scripts at different points in time, such as pre-commit, post-merge, and pose-checkout, among others. 24 | 25 | In our `exercise10` branch we should have a little bash script called `pre-commit`. This script will run before a commit - it will call git to get the names of any .sh script that's staged to be committed, then check the first two characters to see if they match `#!` (called a shebang line in bash). If this line doesn't exist, it will throw an error and not allow the change to be committed. 26 | 27 | Move this script into your git hooks folder, make sure it's executable, then make a new shell script - without a #! line - and try to commit it: 28 | 29 | ``` 30 | $> cp pre-commit .git/hooks/pre-commit 31 | 32 | $> chmod +x .git/hooks/pre-commit 33 | 34 | $> echo "Bad bash script" > test_script.sh 35 | 36 | $> git add test_script.sh 37 | 38 | $> git commit -m "Adding a new test script" 39 | No shebang found! Not allowed to commit! 40 | ``` 41 | 42 | Oh no! Let's fix our test script so that it has a valid #! line and try committing it again: 43 | 44 | ``` 45 | $> echo '#!/bin/bash\n Good bash script' > test_script.sh 46 | 47 | $> git add test_script.sh 48 | 49 | $> git commit -m "Adding a new test script" 50 | [exercise10 6b346ab] Adding a new test script 51 | 1 file changed, 1 insertion(+) 52 | create mode 100644 test_script.sh 53 | 54 | ``` 55 | Success! This was a very simple example, but it's easy to see how this can be extended to do proper linting or checking of code - or even run unit tests - before commit, to decrease the chances of bad code being checked-in. 56 | 57 | #### End of Exercise Ten -------------------------------------------------------------------------------- /exercises/Exercise11-GitHubAPI.md: -------------------------------------------------------------------------------- 1 | # Advanced Git 2 | ## Final Exercise - Using the GitHub API 3 | 4 | 5 | ### Overview 6 | 7 | In this exercise, we'll use the GitHub REST API to get information about ourselves, our repositories, and other repositories on GitHub. 8 | 9 | **Note:** GitHub allows 60 unauthenticated requests per hour. If you hit your limit, and would like to keep exploring proceed to **Bonus Exercise 1** to learn how to create an API token. 10 | 11 | In these examples I'll be using `cURL`, which is a command line tool that allows you to make requests to a server from the terminal. 12 | 13 | If your skill-set is more advanced, you can try a developer library for your language instead. There are GitHub developer libraries for many languages, such as python, javascript, PHP, and more! [GitHub Developer Libraries](https://developer.github.com/v3/libraries/) 14 | 15 | If you're unsure about which integration to use, try the exercises using `cURL` first. 16 | 17 | This GitHub API has many uses, from getting simple profile and repository information to creating Issues programatically. 18 | 19 | ### Prerequisite - Learn to make unauthenticated requests 20 | 21 | We can use the GitHub API unauthenticated for many operations, but GitHub limits us to 60 requests an hour. 22 | 23 | To get information about my user profile without authenticating, run: 24 | 25 | ``` 26 | curl https://api.github.com/users/nnja 27 | ``` 28 | 29 | Try it with your username and check out the results. 30 | This corresponds to the [Users endpoint](https://developer.github.com/v3/users/). 31 | 32 | ### Exercise 33 | 34 | Using the [GitHub API documentation](https://developer.github.com/v3/) answer the following questions: 35 | 36 | 1. How many GitHub users are you following? 37 | 2. Which of your repositories has the most stars? 38 | 3. What languages are present in your favorite repository? 39 | 40 | Hints: 41 | 42 | 1. [Get a single user](https://developer.github.com/v3/users/#get-a-single-user) 43 | 2. [Search Repositories](https://developer.github.com/v3/search/#search-repositories). Stars are in the `stargazer_count`, pass in `stars` as the `sort` parameter. 44 | 3. [List languages for a Repository](https://developer.github.com/v3/repos/#list-languages) 45 | 46 | ## Solutions 47 | 48 | ### Step 1 - Get a Github User 49 | Let's query the Github single user API endpoint to see how many people we're following. Replace the username with your Github username to see personalized results. 50 | 51 | ``` 52 | $> curl "https://api.github.com/users/nnja" 53 | { 54 | "login": "nnja", 55 | "id": 2030983, 56 | "avatar_url": "https://avatars1.githubusercontent.com/u/2030983?v=4", 57 | "gravatar_id": "", 58 | "url": "https://api.github.com/users/nnja", 59 | "html_url": "https://github.com/nnja", 60 | "followers_url": "https://api.github.com/users/nnja/followers", 61 | "following_url": "https://api.github.com/users/nnja/following{/other_user}", 62 | "gists_url": "https://api.github.com/users/nnja/gists{/gist_id}", 63 | "starred_url": "https://api.github.com/users/nnja/starred{/owner}{/repo}", 64 | "subscriptions_url": "https://api.github.com/users/nnja/subscriptions", 65 | "organizations_url": "https://api.github.com/users/nnja/orgs", 66 | "repos_url": "https://api.github.com/users/nnja/repos", 67 | "events_url": "https://api.github.com/users/nnja/events{/privacy}", 68 | "received_events_url": "https://api.github.com/users/nnja/received_events", 69 | "type": "User", 70 | "site_admin": false, 71 | "name": "Nina Zakharenko", 72 | "company": null, 73 | "blog": "nnja.io", 74 | "location": "Portland, OR", 75 | "email": null, 76 | "hireable": null, 77 | "bio": "Developer, pythonista, & speaker.\r\nTeam emacs.\r\nCurrently @venmo, previously @reddit & @recursecenter", 78 | "public_repos": 46, 79 | "public_gists": 21, 80 | "followers": 208, 81 | "following": 69, 82 | "created_at": "2012-07-24T01:53:42Z", 83 | "updated_at": "2017-08-27T09:43:19Z" 84 | } 85 | ``` 86 | It looks like I'm following 69 other users. 87 | 88 | ### Step 2 - What's Our Most Popular Repo? 89 | We'll query the `/search/repositories` endpoint to find out our most starred repo. We want to search by user (`q=user:nnja`), sort by number of stars (`sort=stars`), and for simplicity we'll set `per_page=1` so that we only get the top result. This is a large response so we'll just grep for the line we're looking for: 90 | 91 | ``` 92 | $> curl -s "https://api.github.com/search/repositories?q=user:nnja&sort=stars&per_page=1" | grep "stargazers_count" 93 | "stargazers_count": 114, 94 | ``` 95 | 96 | ### Step 3 - What Languages are in your Favorite Repo? 97 | Github has a handy List Languages endpoint that will return, for a given repo, the number of bytes of code written in any number of languages: 98 | 99 | ``` 100 | $> curl "https://api.github.com/repos/nodejs/node/languages" 101 | { 102 | "JavaScript": 5678164, 103 | "C++": 2072649, 104 | "C": 376581, 105 | "HTML": 163390, 106 | "POV-Ray SDL": 88241, 107 | "Python": 85037, 108 | "DTrace": 37659, 109 | "Makefile": 33997, 110 | "Batchfile": 23763, 111 | "Roff": 15312, 112 | "R": 5359, 113 | "Shell": 1944 114 | } 115 | 116 | ``` 117 | Surprise, surprise - JavaScript makes up a large majority of the `nodejs` repo. 118 | 119 | ### Additional Resources 120 | We've just scratched the surface of what the GitHub API is capable of. 121 | 122 | If you have extra time, continue to the bonus exercises below. 123 | 124 | If you want a deeper dive into creating real-world GitHub API applications, review the [Development Guides](https://developer.github.com/v3/guides/). 125 | 126 | ### Bonus Exercise 1 127 | 128 | Using the GitHub API with a personal access token provides a few benefits: 129 | 130 | - Your rate limit is bumped to 5,000 requests an hour, instead of 60. 131 | - Tokens are revokable. If your token is compromised, you can cancel it. 132 | - Tokens can be given limited scope, to only perform the operations you explicitly authorize them for. 133 | 134 | **Note:** An GitHub personal token is a password! Don't share it with anyone or commit it to a public repository. When you're done using your personal token for testing, a best practice is to revoke it when you're done. 135 | 136 | The GitHub API also supports oAuth, for authentication via applications. 137 | 138 | **Follow these steps to create a personal access token:** 139 | 140 | 1. Visit [https://github.com/settings/tokens](https://github.com/settings/tokens) 141 | 2. Click Generate a New Token 142 | 3. Enter a token description 143 | 4. Select the 'repo' scope, leave all others unchecked. 144 | 5. Store the access code in a safe place, you won't be able to retrieve it again. If you lose it, you'll have to generate a new one. 145 | 146 | 147 | **To use an access token to make CURL Requests:** 148 | 149 | Running the following command with a personal token will return the user details for the authenticated user. 150 | 151 | ``` 152 | curl -i -H 'Authorization: token ' \ 153 | https://api.github.com/user 154 | ``` 155 | 156 | ### Bonus Exercise 2 157 | 158 | If you're a pro at using APIs, and know basic HTML and CSS, you can give the last bonus exercise a try. 159 | 160 | Create a simple webpage that displays your git profile information, as well as some simple statistics. 161 | 162 | Include: 163 | 164 | - Your GitHub profile picture 165 | - Your GitHub bio 166 | - The amount of followers you have 167 | - Your most 'starred' repo 168 | -------------------------------------------------------------------------------- /exercises/Exercise2-StagingAndStashing.md: -------------------------------------------------------------------------------- 1 | # Advanced Git 2 | ## Exercise Two - Staging and Stashing 3 | 4 | ### Overview 5 | In this exercise, we'll take a quick look at interactive staging, unstaging files, and stashing uncommitted changes. 6 | 7 | ### Prerequisite 8 | Clone the `advanced-git-exercises` repository from [github.com/nnja/advanced-git-exercises](https://github.com/nnja/advanced-git-exercises). 9 | Then, checkout the `exercise2` tag to set things up for the following exercise. 10 | 11 | ``` 12 | $> git clone git@github.com:nnja/advanced-git-exercises.git 13 | Cloning into 'advanced-git-exercises'... 14 | remote: Counting objects: 3, done. 15 | remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0 16 | Receiving objects: 100% (3/3), done. 17 | Checking connectivity... done. 18 | 19 | $> cd advanced-git-exercises 20 | 21 | $> git checkout exercise2 22 | Switched to branch 'exercise2' 23 | ``` 24 | 25 | ### Exercise 26 | 1. Use `git ls-files -s` to view the contents of the staging area. 27 | 2. Make a change and try to stage it interactively (`git add -p`). 28 | 3. Use `git reset` to undo the staging of your file. 29 | 4. Stash your change with a unique stash message, then unstash and apply it back to the `exercise2` branch. 30 | 31 | ## Solutions 32 | 33 | ### Step 1 - View the Contents of the Staging Area 34 | 35 | Under the hood, the staging area is a file that contains a list of files, as well as the SHA1 of the version that's in the repository. 36 | 37 | Use `git ls-files -s` to view the `hello.txt` file that we checked in previously. The SHA1 hash displayed points to the blob object - which contains the contents of the file. 38 | 39 | ``` 40 | $> git ls-files -s 41 | 100644 980a0d5f19a64b4b30a87d4206aade58726b60e3 0 hello.txt 42 | ``` 43 | 44 | 45 | ### Step 2 - Interactive Staging 46 | Make a change to your `hello.txt` file, then use `git add -p` to stage your change interactively. The following is a simple example with only one change to commit. With multiple edits to a code file, for example, git will intelligently break up the changes into "hunks." Git will then display sequentially and ask you whether or not you want to stage each hunk. 47 | 48 | ``` 49 | $> echo "This is a test of the emergency git-casting system." >> hello.txt 50 | 51 | $> git add -p 52 | diff --git a/hello.txt b/hello.txt 53 | index 980a0d5..b31a35b 100644 54 | --- a/hello.txt 55 | +++ b/hello.txt 56 | @@ -1 +1,2 @@ 57 | Hello World! 58 | +This is a test of the emergency git-casting system. 59 | Stage this hunk [y,n,q,a,d,/,e,?]? 60 | ``` 61 | If you type `?` and press `enter` after `Stage this hunk`, git will explain the shortcut keys: 62 | 63 | ``` 64 | Stage this hunk [y,n,q,a,d,/,e,?]? 65 | y - stage this hunk 66 | n - do not stage this hunk 67 | q - quit; do not stage this hunk or any of the remaining ones 68 | a - stage this hunk and all later hunks in the file 69 | d - do not stage this hunk or any of the later hunks in the file 70 | g - select a hunk to go to 71 | / - search for a hunk matching the given regex 72 | j - leave this hunk undecided, see next undecided hunk 73 | J - leave this hunk undecided, see next hunk 74 | k - leave this hunk undecided, see previous undecided hunk 75 | K - leave this hunk undecided, see previous hunk 76 | s - split the current hunk into smaller hunks 77 | e - manually edit the current hunk 78 | ? - print help 79 | ``` 80 | 81 | The change you want to change is listed above, where it says `+This is a test of the emergency git-casting system.` The `+` symbol means that a new line was added, and the following is the contents of the new line. Press `y` and `enter` to stage this hunk. You can now see that `hello.txt` has been staged to be committed. 82 | 83 | ``` 84 | $> git status 85 | On branch exercise2 86 | Your branch is up-to-date with 'origin/exercise2'. 87 | Changes to be committed: 88 | (use "git reset HEAD ..." to unstage) 89 | 90 | modified: hello.txt 91 | ``` 92 | 93 | Now that we've staged changes to our hello.txt file, let's run `git ls-files -s` again, to see the contents of our staging area: 94 | 95 | ``` 96 | $> git ls-files -s 97 | 100644 b31a35bc9c5ae5aff4a0f76f7834cc2428408050 0 hello.txt 98 | ``` 99 | 100 | You'll see that the SHA1 of `hello.txt` has changed. It _was_ `980a0` and it's now `b31a3`. 101 | This mechanism is how git knows that the contents of the staging area have changed. 102 | 103 | 104 | ### Step 3 - Unstage your Change 105 | Say you made a mistake while staging your changes and want to start the staging process over, but without losing the changes to your file. Use `git reset` to remove the files from the staging area, without changing the file in the working area. Use `git status` to see that our modifications to `hello.txt` are still there, but they're no longer staged for commit. 106 | 107 | ``` 108 | $> git reset hello.txt 109 | Unstaged changes after reset: 110 | M hello.txt 111 | 112 | $> git status 113 | On branch exercise2 114 | Your branch is up-to-date with 'origin/exercise2'. 115 | Changes not staged for commit: 116 | (use "git add ..." to update what will be committed) 117 | (use "git checkout -- ..." to discard changes in working directory) 118 | 119 | modified: hello.txt 120 | 121 | no changes added to commit (use "git add" and/or "git commit -a") 122 | ``` 123 | 124 | The changes to `hello.txt` are still present in the working area, but they were removed from the staging area before being committed to the repository. 125 | 126 | ### Step 4 - Stash your Changes 127 | Use `git stash` to stash your uncommitted changes. You can unstash these changes later, when you're ready to commit them. It's a good idea to use `git stash save` with a message; this helps you remember what's what if you stash multiple changes. 128 | 129 | Then use `git status` to check the status of your working area. 130 | 131 | ``` 132 | $> git stash save "emergency git-casting" 133 | Saved working directory and index state On exercise2: emergency git-casting 134 | HEAD is now at aceb9e8 Initial commit 135 | 136 | $> git status 137 | On branch exercise2 138 | Your branch is up-to-date with 'origin/exercise2'. 139 | nothing to commit, working directory clean 140 | ``` 141 | 142 | As you can see, our `HEAD` is still pointed at `exercise2`, and our working directory is clean. The major benefit here is that we are now free to change branches, do other work, etc. without our changes getting in the way. 143 | 144 | Now we're ready to apply that change that we stashed. Where is it? 145 | 146 | ``` 147 | $> git stash list 148 | stash@{0}: On exercise2: emergency git-casting 149 | ``` 150 | 151 | Using `git stash list` we can see that we have one stashed change - "emergency git-casting" 152 | 153 | We can get more information using `git stash show`: 154 | 155 | ``` 156 | $> git stash show stash@{0} 157 | hello.txt | 1 + 158 | 1 file changed, 1 insertion(+) 159 | ``` 160 | 161 | There's our change. Let's apply it: 162 | 163 | ``` 164 | $> git stash apply stash@{0} 165 | On branch exercise2 166 | Your branch is up-to-date with 'origin/exercise2'. 167 | Changes not staged for commit: 168 | (use "git add ..." to update what will be committed) 169 | (use "git checkout -- ..." to discard changes in working directory) 170 | 171 | modified: hello.txt 172 | 173 | no changes added to commit (use "git add" and/or "git commit -a") 174 | 175 | $> git diff hello.txt 176 | diff --git a/hello.txt b/hello.txt 177 | index 980a0d5..b31a35b 100644 178 | --- a/hello.txt 179 | +++ b/hello.txt 180 | @@ -1 +1,2 @@ 181 | Hello World! 182 | +This is a test of the emergency git-casting system. 183 | ``` 184 | 185 | Now the change we made earlier to `hello.txt` is back in the working area, but not yet staged for commit. 186 | 187 | Tip: `git stash apply` will automatically apply the last stashed change, so no need to use `stash@{0}` if you want to apply something you just stashed. 188 | 189 | #### End of Exercise Two 190 | -------------------------------------------------------------------------------- /exercises/Exercise3-References.md: -------------------------------------------------------------------------------- 1 | # Advanced Git 2 | ## Exercise Three - References 3 | 4 | ### Overview 5 | In this exercise, we'll take a look at our references (`refs`) and create some lightweight and annotated tags. Then we'll make a dangling commit from a "detached HEAD" state and learn why this isn't a great idea. 6 | 7 | ### Prerequisite 8 | You should have the [`advanced-git-exercises`](https://github.com/nnja/advanced-git-exercises) repository cloned locally. Checkout the `exercise3` branch to begin: 9 | 10 | ``` 11 | $> git checkout exercise3 12 | Switched to branch 'exercise3' 13 | ``` 14 | 15 | ### Exercise 16 | 1. Check the value of your `HEAD` variable (hint: look in `.git`) and confirm you're pointed at the `exercise3` branch. 17 | 2. Use `show-ref` to look at your other heads. 18 | 3. Create a lightweight tag and confirm that it's pointing at the right commit. 19 | 4. Create an annotated tag, and use `git show` to see more information about it. 20 | 5. Get into a "detached HEAD" state by checking out a specific commit, then confirm that your HEAD is pointing at this commit rather than at a branch. 21 | 6. Make a new commit, then switch branches to confirm that you're leaving a commit behind. 22 | 23 | ## Solutions 24 | ### Step 1 - Where's your HEAD? 25 | Assuming you checked out the `exercise3` branch in Step 0, your `HEAD` should be pointing to `exercise3`. You can corroborate this with `git branch`: 26 | 27 | ``` 28 | $> cat .git/HEAD 29 | ref: refs/heads/exercise3 30 | 31 | $> git branch 32 | exercise2 33 | * exercise3 34 | master 35 | ``` 36 | 37 | ### Step 2 - Where are your refs? 38 | Use `git show-ref` to see which commits your HEADs are pointing at. You should see one for every branch you have, as well as every remote branch we've interacted with. Yours may look slightly different. 39 | 40 | ``` 41 | $> git show-ref --heads 42 | 43388fee19744e8893467331d7853a6475a227b8 refs/heads/exercise2 43 | e348ebc1187cb3b4066b1e9432a614b464bf9d07 refs/heads/exercise3 44 | 43388fee19744e8893467331d7853a6475a227b8 refs/heads/master 45 | 43388fee19744e8893467331d7853a6475a227b8 refs/remotes/origin/exercise2 46 | e348ebc1187cb3b4066b1e9432a614b464bf9d07 refs/remotes/origin/exercise3 47 | 43388fee19744e8893467331d7853a6475a227b8 refs/remotes/origin/master 48 | ``` 49 | 50 | You can see for yourself that our `master` branch is pointing to our "Initial commit" 51 | 52 | ``` 53 | $> git cat-file -p 43388fee19744e8893467331d7853a6475a227b8 54 | tree 581caa0fe56cf01dc028cc0b089d364993e046b6 55 | author Nina Zakharenko 1507168309 -0700 56 | committer Nina Zakharenko 1507168309 -0700 57 | 58 | Initial commit 59 | ``` 60 | 61 | Whereas our `exercise3` branch is pointing to our newer commit from Exercise 2: 62 | 63 | ``` 64 | $> git cat-file -p e348ebc1187cb3b4066b1e9432a614b464bf9d07 65 | tree cbcdf5dda7853d595fe0b1942cb0d1d72eb910f3 66 | parent 43388fee19744e8893467331d7853a6475a227b8 67 | author Nina Zakharenko 1507168872 -0700 68 | committer Nina Zakharenko 1507168872 -0700 69 | 70 | Testing the emergency git-casting system 71 | ``` 72 | 73 | 74 | ### Step 3 - Lightweight Tags: 75 | Lightweight tags are simply named pointers to a commit. Make a new tag, then confirm that it points to the correct commit using `show-ref`: 76 | 77 | ``` 78 | $> git tag my-exercise3-tag 79 | 80 | $> git show-ref --tags 81 | e348ebc1187cb3b4066b1e9432a614b464bf9d07 refs/tags/my-exercise3-tag 82 | ``` 83 | Our current HEAD, `38708c...` has now been tagged as `my-exercise3-tag`. 84 | 85 | You can also do a reverse lookup using `git tag --points-at`: 86 | 87 | ``` 88 | $> git tag --points-at e348ebc 89 | my-exercise3-tag 90 | ``` 91 | 92 | ### Step 4 - Annotated Tags: 93 | Annotated tags serve the same function as regular tags, but they also store additional metadata: 94 | 95 | ``` 96 | $> git tag -a "exercise3-annotated-tag" -m "This is my annotated tag for exercise 3" 97 | 98 | $> git show exercise3-annotated-tag 99 | tag exercise3-annotated-tag 100 | Tagger: Nina Zakharenko 101 | Date: Wed Oct 4 19:12:19 2017 -0700 102 | 103 | This is my annotated tag for exercise 3 104 | 105 | commit e348ebc1187cb3b4066b1e9432a614b464bf9d07 106 | Author: Nina Zakharenko 107 | Date: Wed Oct 4 19:01:12 2017 -0700 108 | 109 | Testing the emergency git-casting system 110 | 111 | diff --git a/hello.txt b/hello.txt 112 | index 980a0d5..b31a35b 100644 113 | --- a/hello.txt 114 | +++ b/hello.txt 115 | @@ -1 +1,2 @@ 116 | Hello World! 117 | +This is a test of the emergency git-casting system. 118 | ``` 119 | 120 | Using `git show`, we can see all of the pertinent information about our `exercise3-annotated-tag`. We see the tag metadata at the top - who made the tag and when, as well as the tag message. Below that, we see the commit that was tagged, and then the diff between the tagged commit and its parent. 121 | 122 | ### Step 5 - Detached HEAD 123 | Now we're going to venture into a "detached HEAD" state. Use `git checkout` to checkout the latest commit directly. You'll get a scary-looking warning about your HEAD being detached. You can confirm this by looking at `.git/HEAD` and seeing that it's now pointing to a commit hash, instead of `refs/heads/exercise3` 124 | 125 | ``` 126 | $> git log --oneline 127 | e348ebc Testing the emergency git-casting system 128 | 43388fe Initial commit 129 | 130 | $> git checkout e348ebc 131 | Note: checking out 'e348ebc'. 132 | 133 | You are in 'detached HEAD' state. You can look around, make experimental 134 | changes and commit them, and you can discard any commits you make in this 135 | state without impacting any branches by performing another checkout. 136 | 137 | If you want to create a new branch to retain commits you create, you may 138 | do so (now or later) by using -b with the checkout command again. Example: 139 | 140 | git checkout -b 141 | 142 | HEAD is now at e348ebc... Testing the emergency git-casting system 143 | 144 | $> cat .git/HEAD 145 | e348ebc1187cb3b4066b1e9432a614b464bf9d07 146 | ``` 147 | 148 | ### Step 6 - Create a Dangling Commit 149 | Even though our `HEAD` is now pointing at a specific commit - instead of a branch or tag - we can still make commits. Go ahead and make a new commit, then confirm that our `HEAD` is now pointing at this new commit: 150 | 151 | ``` 152 | $> echo "This is a test file" > dangle.txt 153 | 154 | $> git add dangle.txt 155 | 156 | $> git commit -m "This is a dangling commit" 157 | [detached HEAD 9bdea9e] This is a dangling commit 158 | 1 file changed, 1 insertion(+) 159 | create mode 100644 dangle.txt 160 | 161 | $> git log --oneline 162 | 9bdea9e This is a dangling commit 163 | 38708c1 Testing the emergency git-casting system 164 | aceb9e8 Initial commit 165 | 166 | $> cat .git/HEAD 167 | 9bdea9e5b47e6e4b8453a43a657d5e292fd9b3b5 168 | ``` 169 | 170 | But wait. Because our new commit - `60c4f0e` in this case - was made in a detached `HEAD` state, it doesn't have any references pointing to it. It's not part of a branch, and has no tags. This is called a Dangling Commit. You'll see this warning if you try to switch branches: 171 | 172 | ``` 173 | $> git checkout exercise3 174 | Warning: you are leaving 1 commit behind, not connected to 175 | any of your branches: 176 | 177 | 9bdea9e This is a dangling commit 178 | 179 | If you want to keep it by creating a new branch, this may be a good time 180 | to do so with: 181 | 182 | git branch 9bdea9e 183 | ``` 184 | 185 | Here, git is warning you that you're leaving this commit dangling. If you wish, you may create a new branch that points to this commit. Git does a periodic garbage collection and will eventually delete any commits that don't have a reference pointing to them. 186 | 187 | #### End of Exercise Three 188 | -------------------------------------------------------------------------------- /exercises/Exercise4-MergingAndReReRe.md: -------------------------------------------------------------------------------- 1 | # Advanced Git 2 | ## Exercise Four - Merging and ReReRe 3 | 4 | ### Overview 5 | In this exercise, we'll take a look at the fast-forward merge, learn how to make a non-fast-forward merge, then learn how to use git's Reuse Recorded Resolution (ReReRe) functionality to automate complex merges. 6 | 7 | ### Prerequisite 8 | You should have the [`advanced-git-exercises`](https://github.com/nnja/advanced-git-exercises) repository cloned locally. Checkout the `exercise4` branch: 9 | 10 | ``` 11 | $> git checkout exercise4 12 | Switched to branch 'exercise4' 13 | ``` 14 | 15 | ### Exercise 16 | 1. Merge the `exercise3` branch into `exercise4`, and look at the git log. 17 | 2. Use `git reset --hard HEAD^` to reset your `exercise4` branch back one commit, then use the `--no-ff` option to `git merge` to merge `exercise3` again. Look at the git log, how is it different from the last step? 18 | 3. Make two conflicting changes to `hello.txt` in two different branches. 19 | 4. Enable ReReRe, then merge one branch into the other. 20 | 5. Backup again with `git reset --hard HEAD^`, then attempt the merge again. Notice how ReReRe automatically resolves the conflict for you. 21 | 22 | ## Solutions 23 | 24 | 25 | ### Step 1 - Fast-Forward Merge 26 | You've probably done plenty of git merges. Here we're going to practice making a merge commit without "fast-forwarding." 27 | 28 | On the `exercise4` branch, you should be on the commit labeled "Initial commit." Let's say `exercise3` is our feature branch - we have a new commit in `exercise3` that we want to merge into `exercise4` - "Testing the emergency git-casting system." Let's try a regular merge: 29 | 30 | ``` 31 | $> git checkout exercise4 32 | 33 | $> git merge exercise3 34 | Updating 43388fe..e348ebc 35 | Fast-forward 36 | hello.txt | 1 + 37 | 1 file changed, 1 insertion(+) 38 | ``` 39 | 40 | Easy-peasy. But wait, look at the history: 41 | 42 | ``` 43 | $> git log --oneline 44 | e348ebc Testing the emergency git-casting system 45 | 43388fe Initial commit 46 | ``` 47 | 48 | We see our new commit, `e348ebc`, with it's parent, `43388fe`, our initial commit. But there's nothing to let us know that `e348ebc` was merged into `exercise4` from the `exercise3` branch. This is because this was a "fast-forward" merge, meaning that there were no changes made to `exercise4` - the pointer for `exercise4` was simply updated via "fast-forward" to point to our new commit, `e348ebc`. 49 | 50 | Fast forward commits happen when the base branch (the one you created your branch on) hasn't changed after you branched from it. The history is linear. 51 | 52 | You'll usuaully see a fast-forward happening on the master branch after merging in a feature, like this: 53 | 54 | ![before ff](https://user-images.githubusercontent.com/2030983/31261121-f747408a-aa17-11e7-8377-e009019707a0.png) 55 | 56 | ![after-ff](https://user-images.githubusercontent.com/2030983/31261122-f75375d0-aa17-11e7-9218-4c7030acc137.png) 57 | 58 | ### Step 2 - Non-Fast-Forward Merge 59 | 60 | In a more complicated codebase, merge commits can be very important for determining when certain features or changes were merged in. Let's go back and tell git to make a merge commit even when one isn't strictly necessary. Use `git reset --hard HEAD^` to back things up to the last commit. 61 | 62 | ``` 63 | $> git reset --hard HEAD^ 64 | HEAD is now at 43388fe Initial commit 65 | ``` 66 | 67 | There, `exercise4` is back to pointing at "Initial commit." Now use `git merge --no-ff`. Note: the editor will open and ask you to write a message for this merge. 68 | 69 | ``` 70 | $> git merge --no-ff exercise3 71 | Merge made by the 'recursive' strategy. 72 | hello.txt | 1 + 73 | 1 file changed, 1 insertion(+) 74 | ``` 75 | 76 | There, now we have a merge commit in place to tell us that `e348ebc` was merged into `exercise4` from the `exercise3` branch, along with who, when, and optionally why it was merged. Use the `--graph` argument to `git log` to see this more clearly: 77 | 78 | ``` 79 | $> git log --graph 80 | * commit 7ea8b01a763b19037ab79b17e6a54a41b60d88e2 81 | |\ Merge: 43388fe e348ebc 82 | | | Author: Nina Zakharenko 83 | | | Date: Wed Oct 4 19:21:05 2017 -0700 84 | | | 85 | | | Merge branch 'exercise3' into exercise4 86 | | | 87 | | * commit e348ebc1187cb3b4066b1e9432a614b464bf9d07 88 | |/ Author: Nina Zakharenko 89 | | Date: Wed Oct 4 19:01:12 2017 -0700 90 | | 91 | | Testing the emergency git-casting system 92 | | 93 | * commit 43388fee19744e8893467331d7853a6475a227b8 94 | Author: Nina Zakharenko 95 | Date: Wed Oct 4 18:51:49 2017 -0700 96 | 97 | Initial commit 98 | ``` 99 | 100 | The picture is much clearer now: The `exercise4` branch diverged with the addition of "Testing the emergency git-casting system", then the `exercise3` branch, which included this new commit, was merged back into `exercise4` with the message "Merge branch `exercise3`" 101 | 102 | ![no-ff](https://user-images.githubusercontent.com/2030983/31261123-f756a890-aa17-11e7-8ab9-501af4e44373.png) 103 | 104 | ### Step 3 - Setting up for a Conflict 105 | Here's where things get a little tricky. Let's set up a merge conflict, and use git's Reuse Recorded Resolution function to resolve it automatically for us. This is super useful in situations where you get a large number of repeated merge conflicts, such as when you're refactoring a codebase while others are still making changes. 106 | 107 | Let's set things up so that we're changing the same line from two different branches, creating a merge conflict. You should still be on the `exercise4` branch. Let's create a new branch, called `mundo`. 108 | 109 | ``` 110 | $> git branch 111 | exercise2 112 | exercise3 113 | * exercise4 114 | master 115 | 116 | $> git checkout -b mundo 117 | Switched to a new branch 'mundo' 118 | ``` 119 | 120 | Now, edit `hello.txt` so that instead of "Hello World!" it says "Hello Mundo!" and create a new commit. 121 | 122 | ``` 123 | $> git branch 124 | exercise2 125 | exercise3 126 | exercise4 127 | master 128 | * mundo 129 | 130 | $> # Edit your hello.txt to say "Hello Mundo!" 131 | 132 | $> git status 133 | On branch mundo 134 | Changes not staged for commit: 135 | (use "git add ..." to update what will be committed) 136 | (use "git checkout -- ..." to discard changes in working directory) 137 | 138 | modified: hello.txt 139 | 140 | no changes added to commit (use "git add" and/or "git commit -a") 141 | 142 | $> git add hello.txt 143 | 144 | $> git commit -m "Changing World to Mundo" 145 | [mundo afa34a6] Changing World to Mundo 146 | 1 file changed, 1 insertion(+), 1 deletion(-) 147 | ``` 148 | 149 | Now, go back to our `exercise4` branch, where `hello.txt` should still read "Hello World!" Create a commit that changes this to "Hola World!" 150 | 151 | ``` 152 | $> git checkout exercise4 153 | Switched to branch 'exercise4' 154 | 155 | $> # Edit hello.txt to say "Hola World!" 156 | 157 | $> git add hello.txt 158 | 159 | $> git commit -m "Changing Hello to Hola" 160 | [exercise4 fec9e7b] Changing Hello to Hola 161 | 1 file changed, 1 insertion(+), 1 deletion(-) 162 | ``` 163 | 164 | ### Step 4 - Enable ReReRe and Merge 165 | Before trying to merge, let's enable git's ReReRe functionality. Then, let's try to merge the `mundo` branch into `exercise4` as normal. 166 | 167 | ``` 168 | $> git config rerere.enabled true 169 | 170 | $> git merge mundo 171 | Auto-merging hello.txt 172 | CONFLICT (content): Merge conflict in hello.txt 173 | Recorded preimage for 'hello.txt' 174 | Automatic merge failed; fix conflicts and then commit the result. 175 | ``` 176 | 177 | You can see, as expected, we have a merge conflict that must be resolved manually. However, you should notice a new line: `Recorded preimage for 'hello.txt'` 178 | 179 | Run `git rerere diff` to see what's going on: 180 | 181 | ``` 182 | $> git rerere diff 183 | --- a/hello.txt 184 | +++ b/hello.txt 185 | @@ -1,6 +1,6 @@ 186 | -<<<<<<< 187 | -Hello Mundo! 188 | -======= 189 | +<<<<<<< HEAD 190 | Hola World! 191 | ->>>>>>> 192 | +======= 193 | +Hello Mundo! 194 | +>>>>>>> mundo 195 | This is a test of the emergency git-casting system. 196 | ``` 197 | 198 | This shows us the current state of the resolution - what we started with to resolve and what we've resolved it to. 199 | 200 | Resolve the conflict in `hello.txt` by changing it to say "Hola Mundo!" and run `rerere diff` again: 201 | 202 | ``` 203 | $> git rerere diff 204 | --- a/hello.txt 205 | +++ b/hello.txt 206 | @@ -1,6 +1,2 @@ 207 | -<<<<<<< 208 | -Hello Mundo! 209 | -======= 210 | -Hola World! 211 | ->>>>>>> 212 | +Hola Mundo! 213 | This is a test of the emergency git-casting system. 214 | ``` 215 | 216 | Now things should be clearer - when git sees "Hello Mundo!" on one side of a merge, and "Hola World!" on the other, it will resolve it to "Hola Mundo!" Mark it as resolved and commit it: 217 | 218 | ``` 219 | $> git add hello.txt 220 | 221 | $> git commit -m "Merging in mundo branch" 222 | [exercise4 ff91b70] Merging in mundo branch 223 | ``` 224 | 225 | ### Step 5 - Back up and Merge Again 226 | Now let's back up to just before the last commit, and try the merge again - this time with the help of ReReRe: 227 | 228 | ``` 229 | $> git reset --hard HEAD^ 230 | HEAD is now at fec9e7b Changing Hello to Hola 231 | 232 | $> git merge mundo 233 | Auto-merging hello.txt 234 | CONFLICT (content): Merge conflict in hello.txt 235 | Resolved 'hello.txt' using previous resolution. 236 | Automatic merge failed; fix conflicts and then commit the result. 237 | 238 | $> cat hello.txt 239 | Hola Mundo! 240 | This is a test of the emergency git-casting system. 241 | ``` 242 | 243 | This time, our merge still failed, but the conflict was resolved automatically, per the line that says `Resolved 'hello.txt' using previous resolution.` There is no need to resolve the conflict manually, as we can see that our `hello.txt` now correctly says "Hola Mundo!" All we need to do is stage `hello.txt` and commit it. 244 | 245 | #### End of Exercise Four 246 | -------------------------------------------------------------------------------- /exercises/Exercise5-HistoryAndDiffs.md: -------------------------------------------------------------------------------- 1 | # Advanced Git 2 | ## Exercise Five - History and Diffs 3 | 4 | ### Overview 5 | In this exercise, we'll practice making a good commit, take a look at some of the interesting command line arguments for `git log`, use `git show` to get more information about a commit, then take a quick look at `git branch`. 6 | 7 | ### Prerequisite 8 | You should have the [`advanced-git-exercises`](https://github.com/nnja/advanced-git-exercises) repository cloned locally. Checkout the `exercise5` branch: 9 | 10 | ``` 11 | $> git checkout exercise5 12 | Switched to a new branch 'exercise5' 13 | ``` 14 | 15 | ### Exercise 16 | 1. Practice creating a well-crafted commit - look at the format given on the slides for help. 17 | 2. Use `git log` to find commits created since yesterday. Rename a file and use the `--name-status` and `--follow` options to `git log` to track down when the file was renamed, and what it used to be called. Use `--grep` to search within commit messages, and `--diff-filter` to find renamed and modified files from `git log`. 18 | 3. Use `git show` to get more information about a specific git hash. 19 | 4. Try the `--merged` and `--no-merged` options to `git branch` to see which branches have been merged into `master` (or not). 20 | 21 | ## Solutions 22 | 23 | ### Step 1 - Make a Good Commit 24 | We've all made commits with short, silly, or otherwise unhelpful messages. Let's practice making a solid commit message for use in this example. 25 | 26 | ``` 27 | # Change your `hello.txt` to say [greeting] [noun]! 28 | 29 | $> cat hello.txt 30 | [greeting] [noun]! 31 | This is a test of the emergency git-casting system. 32 | 33 | # Rename hello.txt to hello.template 34 | 35 | $> git mv hello.txt hello.template 36 | 37 | $> git add hello.template 38 | 39 | $> git commit 40 | 41 | # This will open your text editor 42 | # Type the following... 43 | Replacing greeting with tokens for i18n 44 | 45 | Currently, hello.txt contains both Spanish and English. 46 | Let's replace Hola with a [greeting] token, and Mundo 47 | with a [noun] token. That way, we can localize hello.txt for 48 | any language! 49 | 50 | # Save and exit your editor 51 | 52 | [exercise5 4b2b90e] Replacing greeting with tokens for i18n 53 | 1 file changed, 1 insertion(+), 1 deletion(-) 54 | ``` 55 | 56 | ### Step 2 - Git Log 57 | Let's take a look at our new commit using `git log`. First we'll see how to see all commits in the log since yesterday: 58 | 59 | ``` 60 | $> git log --since="yesterday" 61 | commit 4b2b90ec4f526139ca9c81e22174ebf5b9c56b52 62 | Author: Nina Zakharenko 63 | Date: Wed Oct 4 20:46:45 2017 -0700 64 | 65 | Replacing greeting with tokens for i18n 66 | 67 | Currently, hello.txt contains both Spanish and English. 68 | Let's replace Hola with a [greeting] token, and Mundo 69 | with a [noun] token. That way, we can localize hello.txt for 70 | any language! 71 | ``` 72 | 73 | Things can get tricky when trying to track down changes to files when they've been renamed. Here we see how to use `git log` to find the commit where `hello.txt` was renamed to `hello.template`. The `--follow` command will continue following the file backward in history, showing its filename for every commit: 74 | 75 | ``` 76 | $> git log --name-status --follow --oneline hello.template 77 | 4b2b90e Replacing greeting with tokens for i18n 78 | R073 hello.txt hello.template 79 | fec9e7b Changing Hello to Hola 80 | M hello.txt 81 | afa34a6 Changing World to Mundo 82 | M hello.txt 83 | e348ebc Testing the emergency git-casting system 84 | M hello.txt 85 | 43388fe Initial commit 86 | A hello.txt 87 | ``` 88 | 89 | Ever spent too much time trying to track down a commit in Github? Use `git log --grep` to quickly find a commit. We can chain this with other flags. Let's find all the internationalization commits that the author `Nina` has added in the last two weeks: 90 | 91 | ``` 92 | $> git log --grep=i18n --author=nina --since=2.weeks 93 | commit 4b2b90ec4f526139ca9c81e22174ebf5b9c56b52 94 | Author: Nina Zakharenko 95 | Date: Wed Oct 4 20:46:45 2017 -0700 96 | 97 | Replacing greeting with tokens for i18n 98 | 99 | Currently, hello.txt contains both Spanish and English. 100 | Let's replace Hola with a [greeting] token, and Mundo 101 | with a [noun] token. That way, we can localize hello.txt for 102 | any language! 103 | ``` 104 | 105 | We can use `--diff-filter` to find commits where files have been renamed: 106 | 107 | ``` 108 | $> git log --diff-filter=R --find-renames 109 | 4b2b90e Replacing greeting with tokens for i18n 110 | ``` 111 | 112 | Or to find commits where files have been modified: 113 | 114 | ``` 115 | $> git log --diff-filter=M --oneline 116 | fec9e7b Changing Hello to Hola 117 | afa34a6 Changing World to Mundo 118 | e348ebc Testing the emergency git-casting system 119 | ``` 120 | 121 | ### Step 3 - Git Show 122 | Now that we've mastered `git log`, how do we actually see what happened in a commit? Let's use `git show` to find out. 123 | 124 | ``` 125 | $> git log --grep=i18n --oneline 126 | 4b2b90e Replacing greeting with tokens for i18n 127 | 128 | # Let's see the full commit and diff for 4b2b90e 129 | 130 | $> git show 4b2b90e 131 | commit 4b2b90ec4f526139ca9c81e22174ebf5b9c56b52 132 | Author: Nina Zakharenko 133 | Date: Wed Oct 4 20:46:45 2017 -0700 134 | 135 | Replacing greeting with tokens for i18n 136 | 137 | Currently, hello.txt contains both Spanish and English. 138 | Let's replace Hola with a [greeting] token, and Mundo 139 | with a [noun] token. That way, we can localize hello.txt for 140 | any language! 141 | 142 | diff --git a/hello.template b/hello.template 143 | new file mode 100644 144 | index 0000000..a6c57ac 145 | --- /dev/null 146 | +++ b/hello.template 147 | @@ -0,0 +1,2 @@ 148 | +[greeting] [noun]! 149 | +This is a test of the emergency git-casting system. 150 | diff --git a/hello.txt b/hello.txt 151 | deleted file mode 100644 152 | index 7018e35..0000000 153 | --- a/hello.txt 154 | +++ /dev/null 155 | @@ -1,2 +0,0 @@ 156 | -Hola Mundo! 157 | -This is a test of the emergency git-casting system. 158 | 159 | # Show the files changed in 4b2b90e 160 | 161 | $> git show 4b2b90e --stat --oneline 162 | 4b2b90e Replacing greeting with tokens for i18n 163 | hello.template | 2 ++ 164 | hello.txt | 2 -- 165 | 2 files changed, 2 insertions(+), 2 deletions(-) 166 | ``` 167 | 168 | ### Step 4 - Git Branch 169 | Let's say you're working on a complicated codebase with a `master` branch and lots of feature branches. Some of your coworkers forget to clean up their branches when they're done (we're all guilty). Which branches have been merged into `master` and can be cleaned up? Which branches haven't been merged yet? If you've been following along, yours may look different. 170 | 171 | ``` 172 | $> git branch --merged master 173 | exercise2 174 | exercise4 175 | master 176 | 177 | $> git branch --no-merged master 178 | exercise3 179 | * exercise5 180 | mundo 181 | ``` 182 | 183 | ### End of Exercise Five -------------------------------------------------------------------------------- /exercises/Exercise6-FixingMistakes.md: -------------------------------------------------------------------------------- 1 | # Advanced Git 2 | ## Exercise Six - Fixing Mistakes 3 | 4 | ### Overview 5 | In this exercise, we'll practice reverting a file and cleaning our repo. Then we'll take a deeper look at `git reset` and `git revert` to go back in time. 6 | 7 | ### Prerequisite 8 | You should have the [`advanced-git-exercises`](https://github.com/nnja/advanced-git-exercises) repository cloned locally. Checkout the `exercise6` branch: 9 | 10 | ``` 11 | $> git checkout exercise6 12 | Switched to a new branch 'exercise6' 13 | ``` 14 | 15 | ### Exercise 16 | 1. Make bad changes to a file, then use `git checkout` to fix it. Use `git checkout` to reset your file back to an earlier point in time. 17 | 2. Use `git clean` to remove untracked files from your repo. Remember to use `--dry-run` first. 18 | 3. Stage a change and then use `git reset` to unstage it. Use `git reset --hard` to reset your branch back pointer, staging area, and working area to an earlier commit. Use "mixed mode" to reset your branch back to an earlier commit, then use `ORIG_HEAD` to reset your branch back to where you were. 19 | 4. Practice using `git revert` to safely revert a commit with a new commit. 20 | 21 | ## Solutions 22 | 23 | ### Step 1 - Undo changes in your working area with `git checkout -- ` 24 | We all make mistakes. Let's make one now: 25 | 26 | ``` 27 | $> echo "Bad data" > hello.template 28 | 29 | $> cat hello.template 30 | Bad data 31 | ``` 32 | 33 | Oops, we messed up our `hello.template`. No worries, the mistake hasn't been committed yet, so we'll just check out the last committed version of `hello.template` from the repository, overwriting the working area: 34 | 35 | ``` 36 | $> git checkout -- hello.template 37 | 38 | $> cat hello.template 39 | [greeting] [noun]! 40 | This is a test of the emergency git-casting system. 41 | ``` 42 | 43 | By default, if you don't pass arguments to `git checkout`, it assumed you meant to use `HEAD`. 44 | **Note:** Remember that `git checkout --` is a destructive operation. 45 | 46 | And, all is right again. Let's say we want to check out a file from a specific point in time. For example, we want to see what `hello.txt` was like before it was templatized: 47 | 48 | ``` 49 | # Let's find the commit where hello.txt was renamed to hello.template 50 | 51 | $> git log --name-status --follow --oneline hello.template 52 | 4b2b90e Replacing greeting with tokens for i18n 53 | R073 hello.txt hello.template 54 | fec9e7b Changing Hello to Hola 55 | M hello.txt 56 | 57 | # Now let's checkout hello.txt from one commit before then 58 | 59 | $> git checkout fec9e7b -- hello.txt 60 | 61 | $> cat hello.txt 62 | Hola World! 63 | This is a test of the emergency git-casting system. 64 | ``` 65 | 66 | As expected, git has restored a snapshot of our `hello.txt` file from the commit `fec9e7b Changing Hello to Hola`. First, git copied the staging area from that commit. Next it copied the file from the updated staging area into our working area. Because of this, our `hello.txt` file appears added to the staging area. We don't really want to keep it, so let's reset it to unstage it and clear the staging area. 67 | 68 | Then let's go ahead and delete our `hello.template` file: 69 | 70 | ``` 71 | $> git reset HEAD hello.txt 72 | 73 | $> git rm hello.template 74 | rm 'hello.template' 75 | 76 | $> git status 77 | On branch exercise6 78 | Your branch is up-to-date with 'origin/exercise6'. 79 | Changes to be committed: 80 | (use "git reset HEAD ..." to unstage) 81 | 82 | deleted: hello.template 83 | 84 | Untracked files: 85 | (use "git add ..." to include in what will be committed) 86 | 87 | hello.txt 88 | 89 | $> git commit -m "Deleting hello.template" 90 | [exercise6 713f6a1] Deleting hello.template 91 | 1 file changed, 2 deletions(-) 92 | delete mode 100644 hello.template 93 | ``` 94 | 95 | Later on, we decide that deleting `hello.template` was an accident. Let's track down where we deleted it, and bring it back: 96 | 97 | ``` 98 | $> git log --diff-filter=D --oneline -- hello.template 99 | 713f6a1 Deleting hello.template 100 | 101 | # Ah, it was deleted at 713f6a1. Let's use the caret (^) syntax to checkout hello.template from one commit before that 102 | 103 | $> git checkout 713f6a1^ -- hello.template 104 | 105 | $> git status 106 | On branch exercise6 107 | Your branch is ahead of 'origin/exercise6' by 1 commit. 108 | (use "git push" to publish your local commits) 109 | 110 | Changes to be committed: 111 | (use "git reset HEAD ..." to unstage) 112 | 113 | new file: hello.template 114 | 115 | Untracked files: 116 | (use "git add ..." to include in what will be committed) 117 | 118 | hello.txt 119 | 120 | $> cat hello.template 121 | [greeting] [noun]! 122 | This is a test of the emergency git-casting system. 123 | ``` 124 | 125 | Excellent, we now have our `hello.template` file back in our staging area and working area. 126 | 127 | ### Step 2 - Clean your Repo 128 | We should still have this old `hello.txt` file sitting around, cluttering up our repo. We can use `git clean` to blow out anything that isn't tracked by git. We'll do a dry run first, just to be safe: 129 | 130 | ``` 131 | $> git clean --dry-run 132 | Would remove hello.txt 133 | 134 | $> git clean -f 135 | Removing hello.txt 136 | ``` 137 | 138 | All clean! 139 | 140 | ### Step 3 - Git Reset 141 | But wait, we never recommitted our `hello.template` file after we deleted it. It should still be staged for commit: 142 | 143 | ``` 144 | $> git status 145 | On branch exercise6 146 | Your branch is ahead of 'origin/exercise6' by 1 commit. 147 | (use "git push" to publish your local commits) 148 | Changes to be committed: 149 | (use "git reset HEAD ..." to unstage) 150 | 151 | new file: hello.template 152 | ``` 153 | 154 | Let's use `git reset` to unstage it. By default, if you don't specify an argument to `git reset`, it'll assume you meant `HEAD`. 155 | 156 | ``` 157 | $> git reset -- hello.template 158 | 159 | $> git status 160 | On branch exercise6 161 | Your branch is ahead of 'origin/exercise6' by 1 commit. 162 | (use "git push" to publish your local commits) 163 | Untracked files: 164 | (use "git add ..." to include in what will be committed) 165 | 166 | hello.template 167 | ``` 168 | 169 | We can see that `hello.template` is now untracked, because it was deleted in the latest commit. Remember that using `git reset` on a file instead of a commit works with `mixed` mode. That means it updates the copy in the staging area, but it keeps the copy in the working area. 170 | 171 | Along with unstaging the file from `HEAD`, you can also reset individual files in the staging area to a specific point in time. 172 | 173 | ``` 174 | # Let's find the commit before we deleted hello.template 175 | 176 | #> git log --oneline 177 | 713f6a1 Deleting hello.template 178 | 4b2b90e Replacing greeting with tokens for i18n 179 | 180 | # Let's remove the untracked copy of hello.template 181 | $> rm hello.template 182 | 183 | $> cat hello.template 184 | cat: hello.template: No such file or directory 185 | 186 | # Reset hello.template back to 4b2b90e 187 | # This will update the staging area with 'hello.template' from that commit. 188 | 189 | $> git reset 4b2b90e -- hello.template 190 | 191 | $> git status 192 | On branch exercise6 193 | Your branch is ahead of 'origin/exercise6' by 1 commit. 194 | (use "git push" to publish your local commits) 195 | 196 | Changes to be committed: 197 | (use "git reset HEAD ..." to unstage) 198 | 199 | new file: hello.template 200 | 201 | Changes not staged for commit: 202 | (use "git add/rm ..." to update what will be committed) 203 | (use "git checkout -- ..." to discard changes in working directory) 204 | 205 | deleted: hello.template 206 | 207 | 208 | # But git reset won't update the working area. 209 | $> cat hello.template 210 | cat: hello.template: No such file or directory 211 | 212 | # At this point, we could commit the file back into our repository if we wanted to. 213 | ``` 214 | 215 | But let's say we really screwed something up, and want to reset everything (the working area and the staging area) back to the latest commit we made. Warning: this will overwrite tracked files and could cause you to lose work. 216 | 217 | ``` 218 | $> git reset --hard HEAD 219 | HEAD is now at 713f6a1 Deleting hello.template 220 | ``` 221 | 222 | What happens if you make a mistake using `git reset`? Git keeps a copy of your `HEAD` in a variable called `ORIG_HEAD`, to help you get back to where you want to be. 223 | 224 | ``` 225 | $> git log -2 --oneline 226 | 713f6a1 Deleting hello.template 227 | 4b2b90e Replacing greeting with tokens for i18n 228 | 229 | # Reset our repo back to 4b2b90e 230 | 231 | $> git reset 4b2b90e 232 | 233 | $> git log -1 --oneline 234 | 4b2b90e Replacing greeting with tokens for i18n 235 | 236 | $> git reset ORIG_HEAD 237 | 238 | $> git log -1 --oneline 239 | 713f6a1 Deleting hello.template 240 | ``` 241 | 242 | And there we are, back at the commit where we deleted the file `hello.template` 243 | 244 | ### Step 4 - Git Revert 245 | Let's say we want to undo deleting `hello.template`, but don't want to alter history. Reverts don't look as nice in your history, but are a safer option when working with collaborators. 246 | 247 | ``` 248 | $> git log -1 --oneline 249 | 713f6a1 Deleting hello.template 250 | 251 | # Let's revert the last commit. 252 | 253 | $> git revert 713f6a1 254 | 255 | # This will open your editor for you to write a commit message for the reverting commit 256 | 257 | [exercise6 9b2c3b3] Revert "Deleting hello.template" 258 | 1 file changed, 2 insertions(+) 259 | create mode 100644 hello.template 260 | ``` 261 | 262 | There, we've successfully brought `hello.template` back from the dead, and we have a revert commit to show others exactly what happened. Plus, we didn't have to change history, so this is a good method to use if the changes you want to revert have already been pushed to your origin. 263 | 264 | #### End of Exercise Six 265 | -------------------------------------------------------------------------------- /exercises/Exercise7-RebaseAndAmend.md: -------------------------------------------------------------------------------- 1 | # Advanced Git 2 | ## Exercise Seven - Rebase and Amend 3 | 4 | ### Overview 5 | In this exercise, we'll practice amending commits, then we'll try a normal rebase and an interactive rebase. 6 | 7 | ### Prerequisite 8 | You should have the [`advanced-git-exercises`](https://github.com/nnja/advanced-git-exercises) repository cloned locally. Checkout the `exercise7` branch: 9 | 10 | ``` 11 | $> git checkout exercise7 12 | Switched to a new branch 'exercise7' 13 | ``` 14 | 15 | ### Exercise 16 | 1. Make a commit, then practice using the `--amend` option to make another change to the previous commit. 17 | 2. Make two non-conflicting changes to two different branches. Rebase one branch onto the other. 18 | 3. Make another change to your current branch. Use an interactive rebase (`git rebase -i`) to rebase the two branches. Try squashing your two commits and rewording the message during the rebase. 19 | 20 | 21 | ## Solutions 22 | 23 | ### Step 1 - Amend a Commit 24 | Create two new files, `first.txt` and `second.txt`, then commit the first but not the second: 25 | 26 | ``` 27 | $> echo "This is the first file" > first.txt 28 | 29 | $> echo "This is the second file" > second.txt 30 | 31 | $> git add first.txt 32 | 33 | $> git commit -m "Committing two new files" 34 | [exercise7 8adf686] Committing two new files 35 | 1 file changed, 1 insertion(+) 36 | create mode 100644 first.txt 37 | ``` 38 | 39 | Oh no, we forgot to include the second file in our commit. No worries, as long as we haven't pushed our commit to a remote repository (like origin), we can amend the last commit to include the other file. 40 | 41 | ``` 42 | $> git add second.txt 43 | 44 | $> git commit --amend 45 | 46 | # This will open your editor, and let you amend your commit message 47 | 48 | [exercise7 b4952f3] Committing two new files 49 | Date: Wed Oct 4 23:06:48 2017 -0700 50 | 2 files changed, 2 insertions(+) 51 | create mode 100644 first.txt 52 | create mode 100644 second.txt 53 | 54 | # Confirm that we've committed both files now 55 | ``` 56 | 57 | There we go, we've fixed the commit to contain both `first.txt` and `second.txt`. Notice that the SHAs are different between the original commit (`8adf686`) and the amended commit (`b4952f3`). Commits can't be edited, so a new commit with the changed data was created and the old commit was replaced. 58 | 59 | ### Step 2 - Set up for a Rebase 60 | Let's get things set up for a simple rebase demo. Checkout `master`, and let's pretend that we have a new feature branch, called `exercise7-2`. 61 | 62 | ``` 63 | $> git checkout master 64 | Switched to branch 'master' 65 | Your branch is up-to-date with 'origin/master'. 66 | 67 | $> git checkout -b exercise7-2 68 | Switched to a new branch 'exercise7-2' 69 | 70 | $> git log --oneline 71 | 43388fe Initial commit 72 | ``` 73 | 74 | Now let's create a new feature and commit it to our feature branch: 75 | 76 | ``` 77 | $> echo "New feature" > feature.txt 78 | 79 | $> git add feature.txt 80 | 81 | $> git commit -m "Adding a new feature" 82 | [exercise7-2 edaa170] Adding a new feature 83 | 1 file changed, 1 insertion(+) 84 | create mode 100644 feature.txt 85 | ``` 86 | 87 | At the same time, let's pretend that our `master` branch has continued to evolve while we were working on this feature: 88 | 89 | ``` 90 | $> git checkout master 91 | Switched to branch 'master' 92 | Your branch is up-to-date with 'origin/master'. 93 | 94 | # The double arrow >> means 'append' to the file instead of overwrite. 95 | $> echo "Master has continued to change" >> hello.txt 96 | 97 | $> git add hello.txt 98 | 99 | $> git commit -m "Master has continued to change" 100 | [master a2c699b] Master has continued to change 101 | 1 file changed, 1 insertion(+) 102 | ``` 103 | 104 | Switching back to our feature branch. It's a good idea to periodically merge in the `master` branch, to keep things up to date and minimize the number of conflicts when the feature branch is eventually merged into `master`. Instead of creating unsightly merge commits though, let's use rebase to replay our feature commits on top of `master`'s commits. 105 | 106 | ``` 107 | $> git checkout exercise7-2 108 | Switched to branch 'exercise7-2' 109 | 110 | $> git rebase master 111 | First, rewinding head to replay your work on top of it... 112 | Applying: Adding a new feature 113 | 114 | # Check that the changes to master are incorporated into our feature branch 115 | 116 | $> git log --oneline 117 | e83fafa Adding a new feature 118 | a2c699b Master has continued to change 119 | 43388fe Initial commit 120 | ``` 121 | 122 | **Tip:** When working on a feature branch that's likely to conflict, I prefer to rebase from master often and fix conflicts as they come up. This way, I'm not stuck with a huge disastrous merge full of conflicts when I'm done with my feature and ready to merge it back to master. 123 | 124 | 125 | ### Step 3 - Interactive Rebase 126 | Let's set up our feature branch for a very simple interactive rebase. Add another new feature and commit it: 127 | 128 | ``` 129 | $> echo "Another new feature" > another_feature.txt 130 | 131 | $> git add another_feature.txt 132 | 133 | $> git commit -m "Adding another new feature" 134 | [exercise7-2 6449351] Adding another new feature 135 | 1 file changed, 1 insertion(+) 136 | create mode 100644 another_feature.txt 137 | ``` 138 | 139 | Now we have two new commits on top of `master`. 140 | 141 | ``` 142 | # passing -n 3 to log lets us see the last 3 commits 143 | 144 | $> git log -n 3 --oneline 145 | 8470d04 (HEAD -> exercise7-2) Adding another new feature 146 | 64db08a Adding a new feature 147 | ce8865e (master) Master has continued to change 148 | ``` 149 | 150 | When we're done with our feature, we want to clean these commits up by combining them using `squash`, and changing the commit message using `reword`. 151 | 152 | Start the interactive rebase by passing in one commit *before* the one you want to start rebasing from. In this case, we want to rebase commit `64db08a` and the commit after it. 153 | 154 | We have some options for which ref to use. All the options below will get the same result: 155 | 156 | - `HEAD~2` - 2 commits before `HEAD` 157 | - `64db08a^` - points to one commit back - parent `ce8865e` 158 | - `ce8865e` - the actual commit before the one we want to rebase from 159 | - `master` - since `master` points to `ce8865e` 160 | 161 | ``` 162 | $> git rebase -i HEAD~2 163 | ``` 164 | 165 | Your editor will open and you should see the following: 166 | 167 | ``` 168 | pick edaa170 Adding a new feature 169 | pick 6449351 Adding another new feature 170 | 171 | # Rebase a2c699b..6449351 onto a2c699b (2 command(s)) 172 | # 173 | # Commands: 174 | # p, pick = use commit 175 | # r, reword = use commit, but edit the commit message 176 | # e, edit = use commit, but stop for amending 177 | # s, squash = use commit, but meld into previous commit 178 | # f, fixup = like "squash", but discard this commit's log message 179 | # x, exec = run command (the rest of the line) using shell 180 | # d, drop = remove commit 181 | # 182 | # These lines can be re-ordered; they are executed from top to bottom. 183 | # 184 | # If you remove a line here THAT COMMIT WILL BE LOST. 185 | # 186 | # However, if you remove everything, the rebase will be aborted. 187 | # 188 | # Note that empty commits are commented out 189 | ``` 190 | 191 | The two feature commits we added to `exercise7-2` are listed in top-down order, and they default to pick, meaning the commits will be used as-is. Let's change the first one to `reword`, and the second to `squash`. This will re-open the editor twice - once to rename the first commit, and again to squash, or combine, the two commits. 192 | 193 | In the first editor, change the commit message "Adding a new feature" to "Adding two new features" 194 | 195 | In the second editor, delete the second message and leave only "Adding two new features" 196 | 197 | You should see something similar: 198 | 199 | ``` 200 | detached HEAD dd693ff] Adding two new features 201 | Date: Wed Oct 4 23:54:35 2017 -0700 202 | 1 file changed, 1 insertion(+) 203 | create mode 100644 feature.txt 204 | [detached HEAD 03de89d] Adding two new features 205 | Date: Wed Oct 4 23:54:35 2017 -0700 206 | 2 files changed, 2 insertions(+) 207 | create mode 100644 another_feature.txt 208 | create mode 100644 feature.txt 209 | Successfully rebased and updated refs/heads/exercise7-2. 210 | ``` 211 | 212 | Now, take a look at your git log: 213 | 214 | ``` 215 | $> git log --oneline 216 | 03de89d Adding two new features 217 | a2c699b Master has continued to change 218 | 43388fe Initial commit 219 | ``` 220 | 221 | Like magic, our two new features have been squashed into one commit, we've reworded the commit message to show this, and our changes are sitting neatly on top of the change from the `master` branch. 222 | 223 | **Tip:** When working on a feature branch, commit early and often to minimize the likelihood of losing work. It'll also make back tracking easier. Once I'm ready to merge my changes, I do an interactive rebase to squash commits where needed, and take the opportunity to write better commit messages. 224 | 225 | #### End of Exercise Seven -------------------------------------------------------------------------------- /exercises/Exercise8-ForksAndRemoteRepos.md: -------------------------------------------------------------------------------- 1 | # Advanced Git 2 | ## Exercise Eight - Forks And Remote Repos 3 | 4 | ### Overview 5 | In this exercise, we'll try creating our own Github fork of the `advanced-git-exercises` repo that we've been working with. Then we'll rename the remotes and try using `git pull --rebase` to do a cleaner pull without adding merge commits. 6 | 7 | ### Prerequisite 8 | You should have the [`advanced-git-exercises`](https://github.com/nnja/advanced-git-exercises) repository cloned locally. Checkout the `master` branch: 9 | 10 | ``` 11 | $> git checkout master 12 | Switched to branch 'master' 13 | ``` 14 | 15 | ### Exercise: 16 | 1. Create your own Github fork of `https://github.com/nnja/advanced-git-exercises`. 17 | 2. Look at your git remotes. Rename your `origin` remote (nnja's copy) to `upstream`. Add your personal fork as the `origin` remote. 18 | 3. Nina will make a change to her copy of the repo. Make a different change to your local repo, then use `git pull --rebase` to merge them. 19 | 20 | ## Solutions 21 | 22 | ### Step 1 - Create your own Fork 23 | In Github, go to `https://github.com/nnja/advanced-git-exercises` and create your own fork of this repo by clicking the Fork button in the top right corner. This should create a copy of the repo that belongs to you. 24 | 25 | ### Step 2 - Set up Remotes 26 | Now, we want to set up our local repository so that your fork is the origin, but we'll keep Nina's version of the repository as a remote called `upstream`. 27 | 28 | First, if you checked out `advanced-git-exercises` from Nina's Github, you should see something like this: 29 | 30 | ``` 31 | $> git remote -v 32 | origin git@github.com:nnja/advanced-git-exercises.git (fetch) 33 | origin git@github.com:nnja/advanced-git-exercises.git (push) 34 | ``` 35 | 36 | Let's rename the `origin` remote to `upstream`: 37 | 38 | ``` 39 | $> git remote rename origin upstream 40 | 41 | $> git remote -v 42 | upstream git@github.com:nnja/advanced-git-exercises.git (fetch) 43 | upstream git@github.com:nnja/advanced-git-exercises.git (push) 44 | ``` 45 | 46 | Now, let's add a new remote - our fork of the repo. We'll call it origin. Substitute the url with the correct url for your fork. You can find the full url on the Github page for your fork, in the dropdown when you click "Clone or download" 47 | 48 | ``` 49 | $> git remote add origin git@github.com:mhenstell/advanced-git-exercises.git 50 | 51 | $> git remote -v 52 | origin git@github.com:mhenstell/advanced-git-exercises.git (fetch) 53 | origin git@github.com:mhenstell/advanced-git-exercises.git (push) 54 | upstream git@github.com:nnja/advanced-git-exercises.git (fetch) 55 | upstream git@github.com:nnja/advanced-git-exercises.git (push) 56 | ``` 57 | 58 | There, now we should have our two remotes set up. This will allow us to push and pull changes to our personal fork, `origin`, while also allowing us to pull in changes from Nina's `upstream` version of the repo. 59 | 60 | ### Step 3 - Pull with Rebase 61 | We should already be familiar with merging commits from a remote repo, but let's take a look at a handy option for pulling - `git pull --rebase`. As you might expect, this pulls down the changes from a remote, but instead of merging them, it rebases - any changes you've made are replayed on top of the remote's changes. This is especially useful if you're working on a continually changing codebase and don't want lots of unsightly merge commits in your history. 62 | 63 | First let's checkout `master`. `master` is still set up to track `upstream/master`, so we'll want to change it to track our personal copy - `origin/master` 64 | 65 | ``` 66 | $> git checkout master 67 | Switched to branch 'master' 68 | Your branch is up-to-date with 'upstream/master'. 69 | 70 | $> git branch --set-upstream-to origin/master 71 | Branch master set up to track local branch origin/master. 72 | ``` 73 | 74 | For this example, we'll need to have a new commit on both the `upstream` repo and our local repo. First Nina will make a change to her version of the repo: 75 | 76 | **Don't follow the next set of instructions. These are the actions that Nina will take.** 77 | 78 | ``` 79 | # Nina does these steps: 80 | 81 | $> echo "Change to upstream" > upstream_change.txt 82 | 83 | $> git add upstream_change.txt 84 | 85 | $> git commit -m "Change to the upstream repo" 86 | [master d9d0989] Change to the upstream repo 87 | 1 file changed, 1 insertion(+) 88 | create mode 100644 upstream_change.txt 89 | 90 | # Push master to the upstream remote specifically, not origin 91 | 92 | $> git push upstream master 93 | Counting objects: 3, done. 94 | Delta compression using up to 4 threads. 95 | Compressing objects: 100% (2/2), done. 96 | Writing objects: 100% (3/3), 313 bytes | 0 bytes/s, done. 97 | Total 3 (delta 0), reused 0 (delta 0) 98 | To git@github.com:nnja/advanced-git-exercises.git 99 | 43388fe..d9d0989 master -> master 100 | 101 | # Reset local repo to Initial commit 102 | 103 | $> git reset --hard HEAD^ 104 | HEAD is now at 43388fe Initial commit 105 | ``` 106 | 107 | Now we'll make a new `feature` branch locally and add a change to it.: 108 | 109 | ``` 110 | $> git checkout -b `feature` 111 | 112 | $> echo "Change to local repo" > local_change.txt 113 | 114 | $> git add local_change.txt 115 | 116 | $> git commit -m "Change to local repo" 117 | [feature c5019be] Change to local repo 118 | 1 file changed, 1 insertion(+) 119 | create mode 100644 local_change.txt 120 | ``` 121 | 122 | Now that we have a change in our local repo feature branch, and a change in our `upstream` repo, let's pull in the changes from the `upstream` repo without an unsightly merge commit in our feature branch: 123 | 124 | ``` 125 | $> git pull --rebase upstream master 126 | From github.com:nnja/advanced-git-exercises 127 | * branch feature -> FETCH_HEAD 128 | First, rewinding head to replay your work on top of it... 129 | Applying: Change to local repo 130 | 131 | $> git log --oneline 132 | 0aa7023 Change to local repo 133 | d9d0989 Change to the upstream repo 134 | 43388fe Initial commit 135 | ``` 136 | 137 | Great! Use `git pull --rebase` frequently to keep your local fork up-to-date with a remote repo without merging. If you open a Pull Request, your history will be clean. 138 | 139 | #### End of Exercise Eight -------------------------------------------------------------------------------- /exercises/Exercise9-AdvancedTools.md: -------------------------------------------------------------------------------- 1 | # Advanced Git 2 | ## Exercise Nine - Advanced Tools 3 | 4 | ### Overview 5 | In this exercise, we'll take a look at some of the advanced features of `git grep`, we'll learn how to "cherry-pick" commits, then we'll take a look at `git blame` and `git bisect`. 6 | 7 | ## Prerequisite 8 | You should have the [`advanced-git-exercises`](https://github.com/nnja/advanced-git-exercises) repository cloned locally. Checkout the `exercise9` branch to begin: 9 | 10 | ``` 11 | $> git checkout exercise9 12 | Switched to branch 'exercise9' 13 | ``` 14 | 15 | ### Exercise 16 | 1. Use `git grep` to search for a string in your git repo. Try using arguments for `git grep` to print line numbers and group results by file. Use the `--cached` option to see the difference between grepping your working area and your staging area. 17 | 2. Try to cherry-pick a commit from one branch into the `exercise9` branch. 18 | 3. Use `git blame` to see who touched a file. Delete a file and commit your change. Use `git blame` again to blame a file from an earlier point in time, before it was deleted. 19 | 4. Start a `git bisect` session and try to find which commit introduced the word "emergency" into `hello.txt` 20 | 21 | ## Solutions 22 | 23 | ### Step 1 - Grepping with Git 24 | We should have three new code files in our `exercise9` branch. Let's use git's built in, super-fast grep functionality to search our code, looking for the word "Python": 25 | 26 | ``` 27 | $> git grep -e "Python" 28 | python_code.py:# This is a Python file 29 | python_code.py: print("Welcome to Python!") 30 | ``` 31 | 32 | Neat, but the output is somewhat confusing. Let's use some options to clean it up: 33 | 34 | ``` 35 | $> git grep --line-number --heading --break -e "Python" 36 | python_code.py 37 | 1:# This is a Python file 38 | 4: print("Welcome to Python!") 39 | ``` 40 | 41 | Now we have matches broken up with line numbers and grouped by file, so it's a little easier to read. 42 | 43 | Let's make a change, and use `git grep` again. Because `python_code.py` is tracked, `git grep` will pick up the change. Try `git grep --cached` - this will only search the version in the staging area, so the new change will be ignored. Then stage the file and try again. 44 | 45 | ``` 46 | $> echo "More Python code" >> python_code.py 47 | 48 | $> git grep --line-number -e "Python" 49 | python_code.py:1:# This is a Python file 50 | python_code.py:4: print("Welcome to Python!") 51 | python_code.py:5:More Python code 52 | 53 | $> git grep --line-number --cached -e "Python" 54 | python_code.py:1:# This is a Python file 55 | python_code.py:4: print("Welcome to Python!") 56 | 57 | # No line number 5! 58 | 59 | $> git add python_code.py 60 | 61 | $> git grep --line-number --cached -e "Python" 62 | python_code.py:1:# This is a Python file 63 | python_code.py:4: print("Welcome to Python!") 64 | python_code.py:5:More Python code 65 | 66 | # There it is! 67 | ``` 68 | 69 | ### Step 2 - Cherry Picking 70 | Let's reset our `python_code.py` to avoid errors when changing branches: 71 | 72 | ``` 73 | $> git checkout python_code.py 74 | ``` 75 | 76 | Now, say we have changes in a specific commit in another branch that we'd like to bring into our current branch, without merging everything from the other branch. We should have two commits in our `exercise9` branch: 77 | 78 | ``` 79 | $> git log --oneline 80 | 88f6e28 Adding bash, python, and java code examples 81 | 43388fe Initial commit 82 | ``` 83 | 84 | Run `git log` on the exercise3 branch. We're looking for the commit hash of the commit "Testing the emergency git-casting system": 85 | 86 | ``` 87 | $> git log exercise3 --oneline 88 | e348ebc Testing the emergency git-casting system 89 | 43388fe Initial commit 90 | 91 | ``` 92 | 93 | The commit we're looking for is `e348ebc` in this copy of the repo (yours may be different). Make sure we're on the `exercise9` branch, and cherry-pick it. 94 | 95 | ``` 96 | $> git cherry-pick e348ebc 97 | [exercise9 331024e] Testing the emergency git-casting system 98 | Date: Wed Oct 4 19:01:12 2017 -0700 99 | 1 file changed, 1 insertion(+) 100 | 101 | $> git log --oneline 102 | 331024e Testing the emergency git-casting system 103 | 88f6e28 Adding bash, python, and java code examples 104 | 43388fe Initial commit 105 | ``` 106 | 107 | Great - as we can see from `git log`, the commit "Testing the emergency git-casting system" was merged into our branch `exercise9`. You'll noticed the cherry-picked commit is on top - unlike if we had rebased our other changes on top of it. 108 | 109 | ### Step 3 - Git Blame 110 | Say you come across some questionable code. How could we tell who the last person to touch it was? `git blame` of course: 111 | 112 | ``` 113 | $> git blame hello.txt 114 | ^43388fe (Nina Zakharenko 2017-10-04 18:51:49 -0700 1) Hello World! 115 | 331024e3 (Nina Zakharenko 2017-10-04 19:01:12 -0700 2) This is a test of the emergency git-casting system. 116 | ``` 117 | 118 | `git blame` also has some useful arguments, such as ignoring whitespace (-w), detecting moved or copied lines (-M), and detecting moved or copied lines from other files in the commit (-C) 119 | 120 | What if a file was deleted, can we still blame it? Of course, you can `git blame` any file from any point in time. Let's delete `java_code.java` and then `blame` it: 121 | 122 | ``` 123 | $> git rm java_code.java 124 | 125 | $> git commit -m "Who uses Java anyway?" 126 | [exercise9 b8e1a56] Who uses Java anyway? 127 | 1 file changed, 7 deletions(-) 128 | delete mode 100644 java_code.java 129 | 130 | # Let's find the commit where java_code.java was deleted 131 | 132 | $> git log --diff-filter=D -- java_code.java 133 | commit b8e1a5692b0ecf1c3a01bce59e640287d0d298f8 134 | Author: Nina Zakharenko 135 | Date: Thu Oct 5 12:19:16 2017 -0700 136 | 137 | Who uses Java anyway? 138 | 139 | # Your commit hash will be different than mine, so take note of it. 140 | 141 | # Now that we have the commit where it was deleted, let's git blame it from one commit before then (using the ^ syntax) 142 | 143 | $> git blame b8e1a5692b0ecf1c3a01bce59e640287d0d298f8^ -- java_code.java 144 | 88f6e286 (Nina Zakharenko 2017-10-05 11:31:34 -0700 1) // This is a Java file 145 | 88f6e286 (Nina Zakharenko 2017-10-05 11:31:34 -0700 2) 146 | 88f6e286 (Nina Zakharenko 2017-10-05 11:31:34 -0700 3) public class HelloWorld { 147 | 88f6e286 (Nina Zakharenko 2017-10-05 11:31:34 -0700 4) public static void main(String[] args) { 148 | 88f6e286 (Nina Zakharenko 2017-10-05 11:31:34 -0700 5) System.out.println("Привет от Java!"); 149 | 88f6e286 (Nina Zakharenko 2017-10-05 11:31:34 -0700 6) } 150 | 88f6e286 (Nina Zakharenko 2017-10-05 11:31:34 -0700 7) } 151 | ``` 152 | 153 | `git blame` also accepts line numbers or regular expressions, if you want to limit your blaming to a range of lines or specific function, rather than blaming the entire file. 154 | 155 | ### Step 4 - Git Bisect 156 | `git bisect` is a really useful function for determining where in history something changed, especially when given a large timeframe. Maybe a bug was introduced last month, but going through every commit since then would be too time-consuming. Say we want to find out where the line "This is a test of the emergency git-casting system." was added to our `hello.txt` file. First we need to know a commit range - we know it's present in our current commit, and we know it wasn't in our Initial commit, so let's start a `git bisect` session with those start and end points: 157 | 158 | 159 | ``` 160 | $> git log --oneline 161 | git log --oneline 162 | b8e1a56 Who uses Java anyway? 163 | 331024e Testing the emergency git-casting system 164 | 88f6e28 Adding bash, python, and java code examples 165 | 43388fe Initial commit 166 | 167 | $> git bisect start b8e1a56 43388fe 168 | Bisecting: 0 revisions left to test after this (roughly 1 step) 169 | [331024e3b1c500b4a30e5975636399bb6542d5f4] Testing the emergency git-casting system 170 | 171 | # Let's test the file... 172 | 173 | $> cat hello.txt 174 | Hello World! 175 | This is a test of the emergency git-casting system. 176 | 177 | # The line is there, so we'll mark this as Bad. Git now moves backward in time... 178 | 179 | $> git bisect bad 180 | Bisecting: 0 revisions left to test after this (roughly 0 steps) 181 | [88f6e2864bd0829c71654f1d19096f436a66ce07] Adding bash, python, and java code examples 182 | 183 | $> cat hello.txt 184 | Hello World! 185 | 186 | # No "emergency" string, so we'll mark this as Good 187 | 188 | $> git bisect good 189 | 331024e3b1c500b4a30e5975636399bb6542d5f4 is the first bad commit 190 | commit 331024e3b1c500b4a30e5975636399bb6542d5f4 191 | Author: Nina Zakharenko 192 | Date: Wed Oct 4 19:01:12 2017 -0700 193 | 194 | Testing the emergency git-casting system 195 | 196 | :100644 100644 980a0d5f19a64b4b30a87d4206aade58726b60e3 b31a35bc9c5ae5aff4a0f76f7834cc2428408050 M hello.txt 197 | ``` 198 | 199 | Excellent - with a simple test - git has helped us figure out that the offending string was introduced in the `331024e...` commit. We can even perform manual tests to see if our commit is good - like loading a webpage. `bisect` is even more powerful with automated tests. By using `git bisect run` with an automated test - such as a unit test or even a simple shell script - we can quickly and easily find bugs introduced into codebases with complex history. 200 | #### End of Example Nine -------------------------------------------------------------------------------- /images/git-in-depth.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:57a8bfbc0cd2b2dde78799cf13d9d0ff8b8cbab32dd6663fc8cde83f3182a17a 3 | size 234093 4 | -------------------------------------------------------------------------------- /presentation/slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnja/advanced-git/f1697d14ad2578a51d2c96d9cb4c259d219f9bbd/presentation/slides.pdf --------------------------------------------------------------------------------