Hardcore PnP git part 2: From new feature idea to pull request

Scenario: You just finished reading my previous blog post on how to do hardcore-git for PnP development. You now realize that you have a great feature you want to add to PnP. And since you have seen the light of how glorious the command line is, you want to know how to do an end-to-end idea-to-PR.

Initial syncing

To get started the first thing we need to do is to pull the latest dev branch from upstream (OfficeDev/PnP) into our local dev branch with git checkout dev && git pull upstream dev. And yes, && means do these two things after eachother if the previous command didn't crash.

Now we can (optionally) push the local branch to our origin remote (for instance pbjorklund/PnP) by running git push origin dev.

There, we are all in sync and can now create a new branch for our feature-to-be. But before we do that, let's dive into branches a little.

Branches

In git branches are just pointers to commits. So new branches are always based on the commit you are currently at.

To illustrate this i just ran git log when on the dev branch on the PnP repo and then I checked out one of Vesas commits with: git checkout a6eb4700684507aaa1f28b6e604e644c01ee006a

Being in this commit we can create a new branch setting this commit as it's starting point in the tree with git checkout -b vesa_test.

Now if we look at git log --decorate=full we will see that the vesa_test branch actually has the commit we checked out earlier as HEAD. Think of HEAD as a pointer to the commit that our current working "place" is. If we diverge farther away we will see where other branches are pointed from here. We can see we have pointers for this commit both to vesa_test and HEAD, we could also see pointers to for instance refs/remotes/origin/dev or similar.

Don't worry if that doesn't really make sense for now, I will show another screenshot at the end of the post that hopefully makes more sense then.

Anyways, this was the long way around for saying

Remember from where you checked out a new branch, it's important

Back to creating a pull request

Alright, let's clean up with git checkout dev && git branch -d vesa_test and then continue on with git checkout dev. From here we can create a new branch from our synced dev with git checkout -b new-cool-feature and start to write some code.

Today I'm going to see if we can't get the TaxonomyPicker Component in sync with the Sample. I'm not going to go into detail into what code was actually changed, that can be found in the resulting pull request.

The first thing we want to do after making changes is to make sure that we don't actually commit anything that we don't want to commit. A perfect example is the web.config that brings no value to the PnP project as it's just an automatic update created by visual studio of the ClientId on deploy.

Staging changes

In git we have the concept of staging changes before we create our commit. That is, we want to tell git exactly what to include in a commit. Git encourages small and frequent commits that can be reverted and still leave the repository in a runnable state. There are even things you can do in git to just stage certain changes in a single file, perhaps I will write about this (interactive rebase for those who can't wait) and other ways to rewrite history in an upcoming blogpost.

So to see what we have been working on we can issue a git status which will show us something like the following

Now in order to review what we have changed let's issue a git diff command. This will pipe the diff into a program called less which is a terminal pager (a way to browse through lots of text in a terminal). This program works with arrow keys or vim keybindings (try pressing uppercase G which will take you to the end of the file). To quit just write a q.

git diff

The diff shows us a lot of interesting information. First it tells us what it's diffing against by saying that A is app.js and B is app.js, that the index is changed and what lines are affected. I don't know if I have ever actually used this information for anything. The thing we really care about is the green + and red - that tells us what actually changed.

Let's stage the changes we want to include in a commit. This can be done the easy way with git add --all . which will add all untracked (when a file not previously known to git is added to the working directory you need to git add thefilesname before it will show up in the "Changes" section. The previous command takes care of this), modified and renamed files.

Oh no! We staged that pesky Web.config file. Hmm, let's unstage it with git reset HEAD Components/Core.TaxonomyPicker/Core.TaxonomyPickerWeb/Web.config

Ok, that looks better. Better review changes one more time before we commit. Let's do that git diff again.

Huh? Why is it only showing me the content of everything that is changed that is NOT going to be commited? Good question! I don't know why this is the default behavior, but it becomes second nature after a while. We can achieve what we meant by doing a git diff --cached which shows us all the code that is staged for a commit.

Thats more like it. Now let's commit our changes.

Creating the commit

By default when you do git commit it will send you into a terminal based text editor. The default in cmder/msysgit is Vim.

Short story: Vim is a text editor where every key on the keyboard is a command. To enter insert mode (to actually write something) something press i. To save and quit press escape and write :wq.

Vim

We are going to issue the following commands to write our commit message.

Press i, then write the subject of the commit on line 1 and then press enter 2 times and write the commit body. (Yes, someday I will make the effort to fix the colorscheme...)
Then we press escape :wq to save our commit.

When in doubt and you want to abort, try :q!. When in doubt but want to save everything you touched try :wqa!

The result is just some short about our new commit. As we can see I checked in way to much code in a single commit.

Now let's say that we accidently forgot a small change that actually belonged in the previous commit. In this case I'm just changing a small string. Instead of doing a whole new commit we can actually very easily rewrite the commit we just did by doing the exact thing we did before, just substitute git commit with git commit --amend. That will bring up the same window with the same text we just wrote, but with our new change included. Just :wq out and you will have "two commits" joined as one.
We could also use it to rewrite the previous commit message without even changing any files.

Rule of thumb: Never rewrite history like this after you have pushed to a remote where there is even the slightest chance that someone had time to pull it. It will make things really uncomfortable

Now we are ready to create our pull request! And that is really simple, I promise.

Creating the pull request

Let's tell github about our changes by pushing up our local branch to our fork at origin with git push origin new-cool-feature (make sure that the local branch name and origin is the same, no reason for now to try to do anything else).

Head over to your fork at github (like mine here).

See that bar above the branch selector? This is what we will use to create our PR.

Github is smart, but it's not always smart enough. When we click the button we will see the following.

Github wants us to tell the OfficeDev team to take our new branch and merge it into the OfficeDev/PnP master branch. We want to target the dev branch.

Thats better. Now we can also see that since we wrote the commit body, github has parsed this as the description of our pull request

And thats it. Now we just have to wait for the powers to be to either accept or reject the pull reqeust.

Oh, and just for reference. Here is what the output of git log --decorate=full looks like in our current feature branch. Makes sense now, doesn't it?

I hope you enjoyed reading this second walk through the land of git. Do you want to learn more? Hate or love git? Let me know in the comments.