INTRODUCTION It is impossible to think of the current software development landscape without Git. Especially with Microsoft making the switch, it’s becoming the source control system of choice for any developer out there. When I started out with Git I remember it being very difficult to grasp. I had never used anything like that before and felt like something was being thrown at me I could not control. Part of that frustration came from being misinformed or rather, not being informed well enough. So how did I end up getting passionate about it? Compared to other version control systems, Git is lightweight, fast and a lot more user friendly. There is almost nothing that can’t be undone using Git. So far, I’ve always found a way to quickly fix the mess I created. I have used a lot of different GUI’s to handle Git and to obfuscate it’s inner working from me but, as most of us know, at a certain point you have no choice but to use the command line. While using Git from a GUI can be perfect 95% of the time, you will lose a lot of time looking for answers in the remaining 5%. Getting to know Git outside of the GUI and straight into the command line can help you save a lot of time and frustration. Given that you have no idea what you might be working on next year, it’s good not to depend on a specific tool other than the command line. Using Windows, my setup consists of posh-git to display useful Git information straight into my PowerShell console. Being the most powerful management console on the Windows platform it should be part of the skillset of anyone working within the Windows ecosystem. If you want to find out more about the setup I used to illustrate this introduction into Git, please visit my blog.
WHUT? Git is a distributed version control system. This means every person can have the entire state and history of a project locally. The result is that you basically remove a single point of failure like you have on centralized version control systems (CVS, Subversion, Perforce, Bazaar, and so on). When it comes to storing data, the way Git works is also quite different from its competitors. Git thinks of its data more like a set of snapshots of a miniature filesystem. Every time you commit, or save the state of your project in Git, it basically takes a picture of what all your files look like at that moment and stores a reference to that snapshot. To be efficient, if files have not changed, Git doesn’t store the file again, just a link to the previous identical file it has already stored. Git thinks about its data more like a stream of snapshots.
Most operations in Git only need local files and resources to operate – generally no information is needed from another computer on your network. If you’re used to a Centralized version control system where most operations have that network latency overhead, this aspect of Git will make you think that the gods of speed have blessed Git with unworldly powers. Because you have the entire history of the project right there on your local disk, most operations seem almost instantaneous. Everything in Git is check-summed before it is stored and is then referred to by that checksum. This means it’s impossible to change the contents of any file or directory without Git knowing about it. This functionality is built into Git at the lowest levels and is integral to its philosophy. You can’t lose information in transit or get file corruption without Git being able to detect it. The mechanism that Git uses for this checksumming is called a SHA-1 hash. This is a 40-character string composed of hexadecimal characters (0–9 and a–f) and calculated based on the contents of a file or directory structure in Git. A SHA-1 hash looks something like this: 24b9da6552252987aa493b52f8696cd6d3b00373
You will see these hash values all over the place in Git because it uses them so much. In fact, Git stores everything in its database not by file name but by the hash value of its contents.
GETTING STARTED – GIT INIT To create a new Git project, which we will now refer to as repository, you can use the git init command. To illustrate the difference between Visual Studio (VS) and Git itself, open VS and create a new C# console project called project_vs in ~\GitSample (~\ stands for your user’s Home directory e.g. C:\Users\You). Make sure you select “Create new Git repository”. Open your PowerShell console and navigate to the GitSample folder and enter the following command: ~\GitSample > git init project_cmd
This will create a folder called project_cmd. When we look at the contents of the folder and compare them to the project folder VS created, we notice a few differences. Looking at the project created from VS, we see our console project and a few other files which can’t be found in our command line project. Let’s start by taking a look at the .git folder (hidden by default). Almost everything Git uses to work can be found in this in this location. The VS project has 2 extra files in there, ms-persist.xml and index. The first file is used by VS to quickly keep track of pending changes; it has to do with a “feature” of VS in the way it displays those. Beware, this file has nothing to do with Git. The index file is available as VS already did some of the work for us. We will come back to this later. Inside the .git folder we can find the following files and folders: hooks
Scripts which can be executed
info
Uninteresting (apart from exclude)
logs
History for different branches
objects
Git’s internal warehouse of blobs
refs
Master copy of all refs in your repo
COMMIT_EDITMSG
The last commit’s message
FETCH_HEAD
The SHA’s of branch/remote heads (fetch)
HEAD
Current ref of what you are looking at
ORIG_HEAD
When merging, SHA you’re merging into
config
Settings for this repository (can override)
description index
Used for Git instaweb The staging area
The config file contains repository specific settings. It can be used to override default settings defined in Git or the .gitconfig file which can be found in your user directory. Looking at the file you can see it
contains useful information about the repository. Once again, when we compare the file generated by VS and the one through our console we notice a few differences. VS decided it is the best choice when it comes to several tools and indicated itself as such. We will look at what these are later down the road, but it is worth noting the difference.
It is safe to say VS will always assume you do not want to use Git outside of its environment. If we want to know what the configuration is for a repository we can quickly do so from the command line. ~\GitSample\project_cmd > git config --list
It is also possible to look at these from within VS. Open Team Explorer and you will see a button called Settings. Tapping this will give you two options, global and repository settings. Global are the settings defined in the .gitconfig file in your user directory, repository settings are stored in the config file found in the .git folder of that repository. Compare both approaches, you will see VS assists you in visualizing certain settings in a nicer way. If you need to alter a value, use the following syntax as a cheat sheet: git config --local <name> <value> -> change a local repository config value git config --global <name> <value> -> change a global config value
It is possible to add an alias for commands in Git. Git commands can be quite complicated to write all the time so adding an alias can help you. Adding an alias is done to the .gitconfig file in your user directory and can be done the same way as changing or adding a setting: git config --global alias.undo-commit â&#x20AC;&#x153;reset --soft HEAD^â&#x20AC;?
If we go up one level in the folder, we see two other files which are not found in the Git repository created from the console. The first one is .gitattributes. This file can optionally be added to every folder to tell Git how to handle certain files. You can see VS created a file that has a lot of information in it, but nothing is being used. The entire file is commented out. Later down the road will you see exactly what it means to uncomment one of those. The second file is the .gitignore file, this file is being used to tell Git which files not to handle or to create an exception on those rules. Like the attributes file, this file can be added to every sub folder to create custom behavior and override settings in the parentâ&#x20AC;&#x2122;s folder .gitignore file. Commonly though, we only add one .gitignore file per project, in the root next to the .git folder.
To exclude files from Git we can use the following syntax: # This is a comment *.log -> ignore all .log files /bin/* -> ignore everything in the bin folder [Dd]ebug/ -> Debug or debug folder
VS already created a really useful .gitignore file containing folders and file which are generated or should not be in your source code. If you want to get a similar experience without building this yourself, you can take a look at gitignore.io to find .gitignore files for all sorts of projects. Just type Visual Studio to get a file almost identical to the one VS generated. Now that we have a repository setup we can work in, it is also possible to create a remote to push changes to. Think of GitHub or BitBucket. As it is possible to create your own server to host repositories, we need to have a certain type of repository which you can talk to (push your changes). A regular repository can also be used to push changes to, but not by default. To create a third repository to use as remote, go to your ~\GitSample directory and type: ~\GitSample > git init project_bare --bare
When we try to do something in the bare repository, we get the following exception. ~\GitSample\project_bare > git status fatal: This operation must be run in a work tree
As it is not possible to do any work straight onto your bare repository we have to tell our other repositories to use that as the remote. This will allow them to push and pull changes to and from the bare repository. When we look at the repository settings in VS you can see Remotes and an add button. Even though the git documentation states you have to add an URL, it can also be the location of a .git folder on your machine. It is perfectly possible in Git to have a different repository to fetch data from than the one you are pushing changes to. VS provides a nice GUI to have this option, in most use cases Iâ&#x20AC;&#x2122;d say we have the same remote repository to both fetch and push changes. By default, the name of your initial remote is origin. Try adding the location of your bare repositoryâ&#x20AC;&#x2122;s .git folder in VS for our console project (~\GitSample\project_vs\.git). Add the same remote to our project_cmd repository from the console: ~\GitSample\project_cmd > git remote add origin ~/GitSample/project_bare
We can specify multiple remotes in Git. The most common use case would be working on open source projects where you would like have both your own repository and the original one found on a git hosting platform like GitHub.
WORKING TREE Time to get to know how Git handles changes to our files. To understand this, we need to have a look at the different stages a file can be in.
We have 4 stages defined:
Untracked: File is there, git doesn’t know about it Unmodified: File is in git, but has no local changes Modified: File is in git, and has local changes Staged: Local changes are ready for committing
Add a new class to our VS console project called Foo. When we take a look at Team Explorer, and go to Changes, we can see two files ready to be committed. Go back to your console and take a look at what Git really sees using git status. ~\GitSample\project_vs > git status On branch master Untracked files: (use "git add <file>..." to include in what will be committed) Foo.cs nothing added to commit but untracked files present (use "git add" to track)
You notice Git knows you added Foo, but the .csproj changes are nowhere to be seen. Go back to VS and press Save All. Now when we take a look in the console and type git status again, you can see it does know about the changed .csproj file. It’s one of those things you tend to forget while working in the console and VS at the same time.
GIT ADD Foo has been added to the project but Git does not know about the file yet. VS highlights [add] next to the file to indicate it is an addition. To add the file to Git from VS you can right-click it and select stage. From the console you can type the following to achieve the same result: ~\GitSample\project_vs > git add .\Foo.cs
To undo this, you can use right-click unstage in VS and the following syntax in the console: ~\GitSample\project_vs > git reset HEAD Foo.cs
Git add is used to stage all specified files and allow them to be committed. Now, you are probably thinking it is a drag to always type git add for every modified or added file. Right you are, so git allows you to add everything apart from ignored files with just one command. In case your .gitignore is properly set up, you can type git add . to stage all changed and modified files. Now, suppose you have a file that contains changes that should actually be split into two commits. In VS this option is not available unless you think about it up front or it will require you to undo your change and redo them. But the console can help you achieve that in a nicer way. When using git add -p, it will allow you to stage selected lines of a file instead of the entire file. Add two lines of code to Foo.cs in Visual Studio. When you save and look at Team Explorer, you can only add the entire file. To do this, type the following command: ~\GitSample\project_vs > git add -p diff --git a/Foo.cs b/Foo.cs index 82a3b03..4898f56 100644 --- a/Foo.cs +++ b/Foo.cs @@ -9,5 +9,7 @@ namespace ConsoleApplication1 { class Foo { +
public string Bar { get; set; }
+
public string Bar2 { get; set; } } }
Stage this hunk [y,n,q,a,d,/,e,?]?
What you are looking at now is a diff of the file. It displays the changes between Gitâ&#x20AC;&#x2122;s history and the working tree. In my example, I added 2 properties. You can see Git displays the added lines preceded by +.
You get several option which you can display using ?. We are interested in e (manually edit). When you select e, it opens your Git editor. By default, this is VIM. In case you do not like VIM it is possible to change it. Set it to your editor of choice using the following syntax (example is Atom): ~\GitSample\project_vs > git config --global core.editor "atom --wait"
Press e and enter to edit the diff. You are not editing the actual file. To remove lines from being staged, remove them from the diff. Say we want to only stage the second line, remove the first one and end up like this: diff --git a/Foo.cs b/Foo.cs index 82a3b03..4898f56 100644 --- a/Foo.cs +++ b/Foo.cs @@ -9,5 +9,7 @@ namespace ConsoleApplication1 { class Foo { +
public string Bar2 { get; set; } } }
Press save and exit your editor. When you check the state of your working tree using git status, you can see the file is both staged and modified. ~\GitSample\project_vs > git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified:
Foo.cs
Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified:
Foo.cs
GIT COMMIT If we want to commit these changes in Git (group changes into one logical unit), we can do so in a single step using VS. Go to Team Explorer, Changes and type a commit message. The rules for a commit message is that the summary can be 50 chars or less. It is possible to add a more detailed description but until now VS does not support this. If you have a hard time coming up with a proper summary this can be seen as an indication that your commit contains too many changes. If so, use the above techniques to alter the files staged to commit. If you pressed commit in VS, you can check the status in the console. Git will tell you only Foo is modified, the other changes are added to the history. If we want to commit from the console, there are several techniques: git commit –m “This is my commit message” git commit –m “This is my commit message” –m “This is my description” git commit -am “This is my commit message”
If we know we want to just add and commit all tracked (known to Git) modified files, we can skip git add and proceed with git commit -am “message” to both add and commit the changes to Git. If we feel the need to add a bit more clarity to the commit, it is possible to add a second -m parameter to hold a larger description.
Committing files to Git will not push propagate them to the remote, they are still local and only visible to you. Remember I mentioned that you will forget to save your .csproj file and just go ahead and commit everything? Past me would then create a new commit stating “Added csproj file” and annoy current me. Instead, Git allows you to add changes to the last commit using the –amend option. So, stage the files you forgot using git add and use the same (or new) message to add those changes to the last commit: ~\GitSample\project_vs > git commit -m “Summary” --amend
VS also allow to do this. In Team Explorer, Changes select actions and ‘Amend Previous Commit’. This technique can also be used to fix typo’s in your previous commit message. Just fix the message and it will be changed in your Git history. Beware, VS cannot do this when it has no changes to commit, the console can, by using the same command as above.
GIT DIFF To know what the changes are and see the difference between Git’s history and staged or modified files, we can use git diff. By default, when creating a repository from VS it highlights itself as the preferred tool for viewing diffs. If change a file, go to Team Explorer, Changes and right click that very file, we get to select “Compare with unmodified” we are treated with a view containing the two versions of our file. The file on the right is what Git has on top of his history (HEAD, tip of the current selected branch) and the file on the right is what is in our working tree. If we want to do this from the console, all we have to do is type: ~\GitSample\project_vs > git difftool
This will also launch VS to show the diff for different files. Suppose we want to see all the changes in a nice overview instead of looping file after file, we can also do that using the –dir-diff option. This will output a file tree view displaying the changes and allow to open the file we want using the configured diff tool. Unfortunately, VS can’t handle this yet. If you try to launch a dir diff from the console now, it will not output anything. Instead, let’s install Meld and get a powerful diff tool for Git. I use Chocolatey to install it; I do not recommend anything else (get to know it). Open an elevated PowerShell console and type: ~\ > choco install meld
With Meld installed we now have to tell Git to add it as our default difftool. Add the following to your .gitconfig to enable it. ~\ > git config --global diff.guitool meld ~\ > git config --global mergetool.meld.path C:\Program Files (x86)\Meld\Meld.exe ~\ > git config --global merge.tool meld
Now that we have set Meld as our tool to handle diffs (and merges, check later down the road), we still have to remove VS as the selected tool in our project_vs repository: ~\GitSample\project_vs > git config --local --unset merge.tool ~\GitSample\project_vs > git config --local --unset diff.tool
Now when we run git difftool –dir-diff it will use Meld to display a tree view of modified files. To see the changes in a file double click it to get a view like in Visual Studio. Interesting to know is that we can also use the –cached option to display the changes between the history and staged files.
GIT LOG Getting to know what changed is really useful when it comes to source control. The fun part with Git is that it can output this to your liking. When you search through the history of a repository you notice how important useful commit messages are. Messages like “Fixed a bug” or “Added project files” are meaningless and will only annoy your colleagues or yourself later in life. In VS, go to Team Explorer, Branches and right click the master branch. You can see the option ‘View History’. Once selected it opens up a nice tree overview of the commits we did together with a short of the commit hash, author, date and message. To display the history in the console, we use the git log command. By default, it shows each commit spread across multiple lines, but we can change all that to make it very pretty just like in VS. To achieve that I added the following alias to my .gitconfig file: [alias] history = log --graph --pretty=format:'%C(white)%h%Creset - %C(bold blue)%d%Creset %s %Cgreen(%cr) %C(cyan)<%an>%Creset'
Quite the mouthful. What it does is add a few cool parameters to the log command. Graph is used to display a tree like view next to the commits. The –pretty-format parameter accepts a predefined format that enables you to display the information the way you want. In my case I added the abbreviated commit hash, the ref names, the relative date and the author name. The output of the git history command shows you this: ~\GitSample\project_vs > git history * 321a31d -
(HEAD -> master) Renamed the last commit (26 minutes ago) <Jan De Dobbeleer>
* fa657a5 -
Test class Foo added (2 days ago) <Jan De Dobbeleer>
* e098bbf -
Add project files. (4 days ago) <Jan De Dobbeleer>
* deed032 -
Add .gitignore and .gitattributes. (4 days ago) <Jan De Dobbeleer>
You can see VS did two commits for us when we created the project. First, it committed the Git specific files, secondly it committed all the project files. Personally, I’m not a big fan of this. Given that most VS project templates contain example classes, I don’t want to have those added to the history straight away. In the git reset chapter I’ll how to undo this. VS has other nice ways to display the history. If we want to know when a certain file was changed you can right-click it, go to Source Control and select ‘View History’. You will see the same tree but only displaying the commits that affected that file. To have the same result in the console, just type the filename after git log or use the self-created git history alias: ~\GitSample\project_vs > git history Foo.cs
It is also possible to display the changes a commit introduced. In VS, right-click the file and select Source Control, Annotate. You will now see who changed the file and when that happened. Double click a commit on the right to open the details. This displays the commit and the files that were changed or added. To see what happened to a file, double click it and it opens a diff that highlights what happened. To achieve the same result in the console, use the git annotate <filename> command. To display the changes a commit introduced, use the git diff <commit hash> command and t will output the same view as VS but in the console. ~\GitSample\project_vs > git annotate Foo.cs fa657a5e
(Jan De Dobbeleer
2016-04-20 20:30:45 +0200
1)<U+FEFF>using System;
fa657a5e
(Jan De Dobbeleer
2016-04-20 20:30:45 +0200
2)using System.Collections.Generic;
fa657a5e
(Jan De Dobbeleer
2016-04-20 20:30:45 +0200
3)using System.Linq;
fa657a5e
(Jan De Dobbeleer
2016-04-20 20:30:45 +0200
4)using System.Text;
fa657a5e
(Jan De Dobbeleer
2016-04-20 20:30:45 +0200
5)using System.Threading;
fa657a5e
(Jan De Dobbeleer
2016-04-20 20:30:45 +0200
6)using System.Threading.Tasks;
fa657a5e
(Jan De Dobbeleer
2016-04-20 20:30:45 +0200
7)
fa657a5e
(Jan De Dobbeleer
2016-04-20 20:30:45 +0200
8)namespace ConsoleApplication1
fa657a5e
(Jan De Dobbeleer
2016-04-20 20:30:45 +0200
9){
fa657a5e
(Jan De Dobbeleer
2016-04-20 20:30:45 +0200
10)
class Foo
fa657a5e
(Jan De Dobbeleer
2016-04-20 20:30:45 +0200
11)
{
321a31da
(Jan De Dobbeleer
2016-04-21 13:24:30 +0200
12)
321a31da
(Jan De Dobbeleer
2016-04-21 13:24:30 +0200
13)
fa657a5e
(Jan De Dobbeleer
2016-04-20 20:30:45 +0200
14)
fa657a5e
(Jan De Dobbeleer
2016-04-20 20:30:45 +0200
15)}
~\GitSample\project_vs > git diff fa657a5e diff --git a/Foo.cs b/Foo.cs index 82a3b03..4898f56 100644 --- a/Foo.cs +++ b/Foo.cs @@ -9,5 +9,7 @@ namespace ConsoleApplication1 { class Foo { +
public string Bar { get; set; }
+
public string Bar2 { get; set; } } }
public string Bar { get; set; } public string Bar2 { get; set; } }
But the fun does not stop there. In the console we have the ability to search through our history. It can be annoying to look for a certain change or when someone changed something, so Git will help you achieve that with git log. # search for commit from a specific person git history --author="Jan" # search for commit messages containing Foo and exclude merge commits git history --grep=Foo --no-merges # search for both Foo in the message and the author, only displays results where both conditions are met git history --grep="Foo" --author="Jan" --all-match
But what if you have to fight your old self or colleagues writing meaningless commit messages and you can’t find what you are looking for? Git has you covered. You can search through the diffs of commits by using the -S parameter. In my case, say I want to know when someone added the silly variable Bar2. I can just go ahead and type: ~\GitSample\project_vs > git history -S'public string Bar2 { get; set; }' * 321a31d -
(HEAD -> master) Renamed the last commit (66 minutes ago) <Jan De Dobbeleer>
Busted. Notice there is no ‘=’ between -S and the string you are querying for. You can see git log is quite powerful and the implementations in VS are far from complete. I use it quite a lot wasn’t it just to be able to blame myself or others for writing silly code or to find something I added once and removed but might want to use again.
GIT CHECKOUT Now that you know how to browse the history, letâ&#x20AC;&#x2122;s have a look at how you can look at how the code was for a certain commit. In that case I can use the git checkout <commit hash> command to go to that specific point in time and play around. In VS I could not find the option to go back to a specific commit o weâ&#x20AC;&#x2122;ll have to use the console for this one. ~\GitSample\project_vs > git checkout e098bbf
Now, when we do that you can see Git tells us something. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout.
Do not be afraid. Your head is not going to fall off. I have seen people freak out because of this before, it sounds very scary. What it means is that the current HEAD is no longer connected to any of the refs. In our case master. To see what means, try typing git history again. It will only output the history from before the commit we are on and the HEAD indication is found on our currently checked out commit. If we want to see where we are compared to master, enter git history master. It shows us that master is ahead of us and the HEAD indication is found once again on our checked out commit. ~\GitSample\project_vs > git checkout e098bbf Note: checking out 'e098bbf'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b <new-branch-name> HEAD is now at e098bbf... Add project files.
~\GitSample\project_vs > git history * e098bbf -
(HEAD) Add project files. (4 days ago) <Jan De Dobbeleer>
* deed032 -
Add .gitignore and .gitattributes. (4 days ago) <Jan De Dobbeleer>
~\GitSample\project_vs > git history master * 321a31d -
(master) Renamed the last commit (78 minutes ago) <Jan De Dobbeleer>
* fa657a5 -
Initial commit for Foo (2 days ago) <Jan De Dobbeleer>
* e098bbf -
(HEAD) Add project files. (4 days ago) <Jan De Dobbeleer>
* deed032 -
Add .gitignore and .gitattributes. (4 days ago) <Jan De Dobbeleer>
If we do some changes and want to commit them, it will work. But you are not on master anymore. Everything you do is done in the void. When checking out master, Git will inform you of this and advice you to do something with those changes. If you donâ&#x20AC;&#x2122;t, you will lose track of the commit hash and it will disappear into the history. It is possible to search for those commits though. The git reflog (reference logs) command outputs the changes for the tip of a reference (HEAD, master). With this we can see exactly how a reference moved over time. Beware, the logs are only kept for a period of time, by default 90 days. If we want to quickly checkout one of the previous commits, we can do so by telling Git to move the dead down the stream using git checkout HEAD~ or git checkout HEAD^. The difference between tilde and carat are the way they move down the history. Tilde will always select the commits first parent. In human language, it will move down the branch you are currently on. Tilde will take the specified parent and default to the first. It is explained in the following image.
Tilde and carat can also be used on commits. Say you want to display what a commit changed, you can use tilde to quickly get the first parent and output the changes: ~\GitSample\project_vs > git diff 68168b3~ 68168b3
UNDO CHANGES We all mess up sometimes. At least, I do. So we need to have a way to undo certain changes. Git has several ways of undoing what you did depending where you are and what you want to do. Suppose we made some changes to a file but do not need them anymore. You could undo the changes in the file itself or simply checkout the latest version in Git’s history. In VS, go to Team Explorer, Changes and right-click the file. You have the option ‘Undo Changes’. What it does is run git checkout <filename> underneath. This will bring back the last known version to Git and you are all set to try again. If we want to do this using the console: ~\GitSample\project_vs > git checkout Foo.cs
But what if you already staged the file? VS does not provide that option at the time. Luckily when running git status in the console, Git already tells us what we need to do for this. Using git reset HEAD <filename> we can move the file back to Modified. In this case, HEAD is optional, you can also just type this: ~\GitSample\project_vs > git reset Foo.cs
When you run git status again, the file is now no longer Staged but Modified. Undo the changes with git checkout and you are all set. But what if we want do more than just that? Well, git reset allows you to do a lot more. To facilitate this, I created several aliases to assist me. [alias] unstage = reset HEAD undo-commit = reset --soft HEAD^ discard = reset --hard HEAD
The --soft option will leave the changes in your working tree. This you can still decide what to do with it. If you no longer require those changes, use the --hard option to lose them forever. You notice we can also remove commits from our Git history. This comes in handy when you want to alter the commit for whatever reason apart from adding files or renaming it (remember git commit -m “message --amend”). In case we want to revert what a specific commit did, we can do so using git revert <reference>. Know that git revert will create a new commit on top of your current branch to achieve this. VS also has the option when selecting a commit in the history view. The result is exactly the same. ~\GitSample\project_vs > git revert HEAD
GIT PUSH Now that we did a lot of work, we want to add it to one of the remotes. Remember we added our bare repository as the remote named origin. We can easily verify this by looking at Team Explorer, Settings and look at the repository settings. If all is well, we can see origin is added as both fetch and pull. To have a look at the remotes from the console, simply type git remote. ~\GitSample\project_vs > git remote origin
To know where it is pointing to, type: ~\GitSample\project_vs > git remote get-url origin ~/GitSample/project_bare/
Let’s try to push the changes from our console first. To do this, simply type git push. You can see Git does not want to do as it has no idea to which branch to push the changes to in origin. VS has moved all this logic in Team Explorer, Sync. And yes, VS can also inform us we do not track a remote branch but it does have a publish option we can use. For some reason, when tapping the ‘learn more’ link, we navigate to a page about pull requests. While that’s all very interesting, it does not help in any way. So, what does VS do when we press Publish? The same Git told us to do when we tried to push our changes to origin: ~\GitSample\project_vs > git push fatal: The current branch master has no upstream branch. To push the current branch and set the remote as upstream, use git push --set-upstream origin master
So let’s try that. ~\GitSample\project_vs > git push --set-upstream origin master Counting objects: 22, done. Delta compression using up to 4 threads. Compressing objects: 100% (21/21), done. Writing objects: 100% (22/22), 6.12 KiB | 0 bytes/s, done. Total 22 (delta 8), reused 0 (delta 0) To ~/Github/project_bare * [new branch]
master -> master
Branch master set up to track remote branch master from origin.
Now that we pushed all our changes, have a look at the folder containing the bare repository. You notice no files have been added. Our solution isn’t there; neither are any of the other files. Yet when we navigate to the bare project in our console and look at the history of master, we can see all the changes: ~\GitSample\project_bare > git history master * 321a31d -
Renamed the last commit (5 hours ago) <Jan De Dobbeleer>
* fa657a5 -
Initial commit for Foo (2 days ago) <Jan De Dobbeleer>
* e098bbf -
Add project files. (4 days ago) <Jan De Dobbeleer>
* deed032 -
Add .gitignore and .gitattributes. (4 days ago) <Jan De Dobbeleer>
When we have a look at how much disc space both repositories take up, we notice project_vs comes in at 136 KB while project_bare only takes up 48.0 KB. That’s just 1/3 of what we really have. When people say Git is lightweight, that’s not a gimmick. The reason Git can do things fast is not only because you do most of the work locally. It’s also because it optimized everything else. You do not want to wait to get changes from a remote, you want it to fly. Our job is not sitting at our desk waiting for stuff to complete (many will argue given certain compile times).
GIT PULL Like git push, pull will talk to our remote. Only this time, it is to bring in changes. In reality, git pull consists of two commands. It’s git fetch and git merge combined. Fetch will bring in all the changes from the remote branches to your repository and make sure your HEAD stays where it is at. Let’s use the console and navigate to project_cmd we created earlier. When you run git status, you notice we are on the master branch and do not have any commits. Make sure you have a remote called origin pointing to project_bare set up as demonstrated in the previous section. Let’s see if we can fetch from the remote: ~\GitSample\project_cmd > git fetch remote: Counting objects: 22, done. remote: Compressing objects: 100% (21/21), done. remote: Total 22 (delta 8), reused 0 (delta 0) Unpacking objects: 100% (22/22), done. From ~/Github/project_bare * [new branch]
master
-> origin/master
Git tells us there is a new branch available. The remote master branch gets translated to origin/master locally so it is not yet added to our local master branch. When we try to take a look at the history, you can see Git still tells us we have no commits yet. So, how do we get the remote changes into our master branch? To do this let’s use the git merge command and take a look at our history once again: ~\GitSample\project_cmd > git merge origin/master ~\GitSample\project_cmd > git history * 321a31d -
(HEAD -> master, origin/master) Renamed the last commit (5 hours ago) <Jan De Dobbeleer>
* fa657a5 -
Initial commit for Foo (2 days ago) <Jan De Dobbeleer>
* e098bbf -
Add project files. (4 days ago) <Jan De Dobbeleer>
* deed032 -
Add .gitignore and .gitattributes. (4 days ago) <Jan De Dobbeleer>
There is still one thing you need to be aware off when using this approach. Our local master branch is not yet attached to the remote master branch. To illustrate this, try doing a git pull. Git will immediately alert you no remote branches are being tracked. The good news is, it once again lets us know what we have to do to fix this:
~\GitSample\project_cmd > git pull There is no tracking information for the current branch. Please specify which branch you want to merge with. See git-pull(1) for details. git pull <remote> <branch> If you wish to set tracking information for this branch you can do so with: git branch --set-upstream-to=origin/<branch> master
~\GitSample\project_cmd > git branch --set-upstream-to=origin/master master Branch master set up to track remote branch master from origin.
And just like that everything is set to work with the remote. When looking at the folder, you can see Git build all the files from looking at the history. We have the exact same solution in project_vs and project_cmd. Of course, running git pull will combine the fetch and merge commands and tell you to set the upstream branch if you want to work with the remote.
BRANCHING When you are used to work with version control systems other than Git, working with branches might scare you. But, like I said before, Git is so lightweight this also applies to branches. There is no difference between a branch and a commit. A branch is just a 41 bytes’ text file pointing towards a commit. To prove this, open Explorer, navigate to ~\GitSample\project_vs\.git\refs\heads and look at the properties of the file called master. You can see it’s only 41 bytes. Open the file with any editor of choice and you’ll see something we’ve seen before, a commit hash. This hash is the pointer to a commit and your current HEAD. When you open the console and do a quick git log in project_cmd, you will see that very commit at the top of the list. Let’s create a new branch. A cool feature within VS is the bottom bar where it displays the current version control info:
It shows us how many local commits we can push to our remote, how many local changes we have, the current repository and the branch we are on. Tapping on either of those either opens Team Explorer or gives us a new set of options. Try tapping on the master branch and you will see the option “New Branch…”. Selecting it will open Team Explorer with more options. The first thing you need to do is select a branch name. Let’s use develop. Obviously the branch starts from master, although when you look at the other options, it also states origin/master. This allows you to add a second branch that also tracks the remote master branch but with a different name. The option “Checkout branch” is selected by default and will thus make sure we are on develop as soon as we press “Create branch”. Enter the name develop and press the button. For some reason I have to press the refresh button in Team Explorer before it knows I’m on the develop branch, but indeed, we are. When looking at the history through VS it shows us that both the master and develop branch point to the same commit. Go back to Explorer and once again look at the ~\GitSample\project_vs\.git\refs\heads folder. It now has 2 files, 1 for master and 1 for develop. The contents of both files are identical as both branches point to the same commit. This also means that moving from one branch to another has no influence on your working tree. You can switch branches and take your modified and staged changes along. This is why branches are such a breeze to work with. They do not get in the way. You can create as many as you’d like and still be able to work fast. Neither are files duplicated, they all just point towards a certain snapshot in your history.
If we want to switch to another branch from the console we can quickly do so using the git checkout command: ~\GitSample\project_vs > git checkout master Switched to branch 'master' Your branch is up-to-date with 'origin/master'.
And just like that we are back on the master branch. Now, if we want to create a branch from the console, we can use two different approaches. If we want to have the same result as what we just did using VS, we can use the git checkout command to both create the branch and switch to it. Navigate to project_cmd and create the develop branch using the following command: ~\GitSample\project_cmd > git checkout -b develop Switched to a new branch 'develop'
If we want to see all the branches we have locally, type git branch: ~\GitSample\project_cmd > git branch * develop master
Git highlights the current by showing a star next to it. In case you want to know about all the branches, both local and remote, use the -a option: ~\GitSample\project_cmd > git branch -a * develop master remotes/origin/master
If we feel the need to delete a branch locally, which is good practice to do once you donâ&#x20AC;&#x2122;t use them anymore, you can do so using Team Explorer, Branches in VS. Right-click a branch and select Delete. Notice you can only delete a branch that is not checked out. In other words, you canâ&#x20AC;&#x2122;t delete the branch you are currently working on. To delete a branch from the console there are 2 options to the branch command you can use. -D will delete it no matter if there is work on the branch that is not yet in another branch, option -d will not allow you to do it and warn you.
To demonstrate this, let’s quickly add a file to our history on the develop branch and try to delete it afterwards: ~\GitSample\project_cmd > echo $null > test.cs ~\GitSample\project_cmd > git add .\test.cs ~\GitSample\project_cmd > git commit -m "Added test" [develop f50f234] Added test 1 file changed, 1 insertion(+) create mode 100644 test.cs
~\GitSample\project_cmd > git checkout master Switched to branch 'master' Your branch is up-to-date with 'origin/master'.
~\GitSample\project_cmd > git branch -d develop error: The branch 'develop' is not fully merged. If you are sure you want to delete it, run 'git branch -D develop'.
If we use the -D option, our branch will be removed: ~\GitSample\project_cmd > git branch -D develop Deleted branch develop (was 2601013).
VS will also warn us for unmerged changes but will go ahead with the delete once we confirm we are aware of consequences. In case you want to delete a branch on the remote, you can do so in VS by right-clicking the branch and selecting “Delete Branch From Remote”. Notice that if you’d like to delete master, it will fail. As master is the default branch, you can’t delete it. To demonstrate this from the console, lets first push the branch to the remote so we can delete it afterwards: ~\GitSample\project_cmd > git push --set-upstream origin develop Total 0 (delta 0), reused 0 (delta 0) To ~/Github/project_bare/ * [new branch]
develop -> develop
Branch develop set up to track remote branch develop from origin.
~\GitSample\project_cmd > git push origin --delete develop To ~/Github/project_bare/ - [deleted]
develop
GIT CLONE Now that we are able to work locally and push to a remote, it might be interesting to know how we can get a remote repository on our machine and work with it, remotes and everything configured from the start. To achieve this, we make use of the git clone command. Let’s start by creating a clone of project_vs. Open VS, Team Explorer and press the green plug icon on top (Manage Connections). You can see we have the options to create a new one, add an existing repository to the list in VS, or Clone a repository. Press Clone and enter the full path to our bare repository c:\Users\<username>\GitSample\project_bare. Let’s clone the bare repository to project_clone. Once we press Clone, VS will alert us the repository has been successfully cloned. Now we have to tell VS to load the repository, if you look at the Solution Explorer it should be empty. To do this, go to Team Explorer’s Home and double-click the solution at the bottom. We are now working in a cloned version of our bare repository. When you look at this repository’s settings in Team Explorer you can clearly see it is linked to our bare repository. This means any changes can be pushed without having to link the branches which came along when we cloned. A new branch will still have to be linked to our remote though. Apart from cloning the bare repository, it is also possible to clone the others. Try to clone project_vs and it will provide the same result only that the remote will be project_vs and not project_bare. Something to take into account. If we want to clone a repository from the console, all we have to do is type: ~\GitSample > git clone C:\Users\Jan\GitSample\project_bare project_clone2 Cloning into 'project_clone3'... done.
When we look at GitHub or BitBucket we can see two options to clone repositories. We have the choice to do it over https or SSH. Working with Git over https will always request your credentials when you work with a remote. This can be overcome by adding your credentials to the credential store but that one will not store them securely. Alternatively, you could also use the Git Credential Manager for Windows (GCM) to securely store your credentials and also not be bothered with entering them all the time once properly set up. But all of those proved to be major overkill compared to using SSH. Most people use SSH on Windows through Putty, but as we are using Git we have access to proper SSH tooling. All we have to do is add the C:\Program Files\Git\usr\bin folder to our PATH and we can start using SSH. Once added, restart your console for the changes to load. Verify if you have ssh.exe in your PATH by typing ssh + ENTER.
Beware, VS is known to remove that folder from your PATH on updates. Not sure why, I’ve seen it and so have my colleagues.
To make use of SSH we need to first create an SSH key. This will consist of a private and public key. The public key will be added to the Git hosting platform, the private key will remain in your possession. To generate a key, use the following command: ~\GitSample > ssh-keygen -t rsa -b 4096 -C your_email@example.com
This will ask for a filename (which can be left blank for the default id_rsa name) and a password (blank = no password). Now we can quickly retrieve the contents of our public key to use on the Git hosting platform (use the correct filename in case you renamed it): ~\GitSample > get-content ~\.ssh\id_rsa.pub | clip.exe
We can now add that key to our ssh-agent. One of the benefits of using posh-git is that it comes with super handy helper functions and one of those is called Start-SSHAgent. This function will start the sshagent and allow us to add our generated key to the agent and use it. In my Microsoft.powershell_profile.ps1, I added the following line: ssh-add ~/.ssh/id_rsa
Now, whenever I start my console, the key is added and I can securely work with GitHub or BitBucket through SSH. I create a new key per device that needs to connect, that way, when Iâ&#x20AC;&#x2122;m done using the device I just remove the public key and it can no longer connect. If anything gets compromised, I do not have to alter my password.
GIT MERGE Now that we can create branches, itâ&#x20AC;&#x2122;s also useful to know how to join the changes from one branch into the other. As it can be conflicts arise, we need to have a way to properly resolve those too. Iâ&#x20AC;&#x2122;m not going to lie, no matter the source control tool, this can be quite the work. Luckily, Git can help us make things a bit easier. By default, Git will try to merge changes by using a fast-forward. This means that it will join the two branches and display them as one in the logs after the merge. If a merge is more complex due to conflicts in the two branches or when the branches have diverged, it will need a new commit on top of the branch you are merging into. This is known as a 3-way merge. Remember we saw that a commit can have multiple parents, this is an example of that. The following image illustrates the difference between a fast-forward and a merge commit. To get started with merging, create a branch called example in VS or the console. On the example branch, change Foo.cs by adding a new property: class Foo { public string Bar { get; set; } public string Bar2 { get; set; } public string Bar3 { get; set; } }
Commit this change immediately and go back to the master branch where we want to include this change into. Once on master we can merge the changes from the example branch by using the git merge command. Our entire workflow in the console would then be as such: ~\GitSample\project_vs > git commit -am "Added Bar3 to Foo.cs" [example b826262] Added Bar3 to Foo.cs 1 file changed, 1 insertion(+)
~\GitSample\project_vs > git checkout master Switched to branch 'master' Your branch is up-to-date with 'origin/master'.
~\GitSample\project_vs > git merge example Updating 17f4c56..b826262 Fast-forward Foo.cs | 1 + 1 file changed, 1 insertion(+)
If we now have a look at our history, we can see Git indeed did a fast-forward merge as stated in the output of the merge command. ~\GitSample\project_vs > git history * b826262 -
(HEAD -> master, example) Added Bar3 to Foo.cs (42 seconds ago) <Jan De Dobbeleer>
* 321a31d -
Renamed the last commit (3 days ago) <Jan De Dobbeleer>
* fa657a5 -
Initial commit for Foo (5 days ago) <Jan De Dobbeleer>
* e098bbf -
Add project files. (7 days ago) <Jan De Dobbeleer>
* deed032 -
Add .gitignore and .gitattributes. (7 days ago) <Jan De Dobbeleer>
Rewind the change on master by using the following command: ~\GitSample\project_vs > git reset HEAD~ --hard
We are now back at the state before the merge and can have a look at merging using VS. Open Team Explorer, Branches and press merge. We now have the option to enter the â&#x20AC;&#x153;fromâ&#x20AC;? branch, which in our case is example. We also have the option to commit the change after merging or not. This is known in the console as the --no-commit option. It can be very useful if you want to see what the result of the merge will be and adjust things instead of blindly committing. Press Merge and VS will tell Git to execute the command. If we right-click master and look at the history, we can see it also executed a fast forward merge. If we want to see what Git does when the branches have diverted, reset the master branch using either VS or the console to the commit before the merge and change Foo.cs in the master branch. Commit that change and do the merge once more. ~\GitSample\project_vs > git merge example Merge made by the 'recursive' strategy.
~\GitSample\project_vs > git history *
adbccc9 -
(HEAD -> master) Merge branch 'example' (4 seconds ago) <Jan De Dobbeleer>
|\ | * b826262 -
(example) Added Bar3 to Foo.cs (29 minutes ago) <Jan De Dobbeleer>
* | 93696ea -
Added a property to Foo.cs (33 seconds ago) <Jan De Dobbeleer>
|/ * 321a31d -
Renamed the last commit (3 days ago) <Jan De Dobbeleer>
* fa657a5 -
Initial commit for Foo (5 days ago) <Jan De Dobbeleer>
* e098bbf -
Add project files. (7 days ago) <Jan De Dobbeleer>
* deed032 -
Add .gitignore and .gitattributes. (7 days ago) <Jan De Dobbeleer>
Git now used a different strategy and created a new commit to merge the two branches. But of course, all of this works perfect when Git can automagically merge the two branches. What if your world is not as perfect as this and you have to resolve conflicts those two branches might have?
First off, Git has a lot of power under the hood to tackle things so you do not need to resolve them. You can try this by renaming the Foo.cs class and perform the merge, it will understand what you did and just go ahead and merge the two branches anyway. And secondly, it can ask you for help when it does not understand what it needs to do. Let’s create a new branch called example2 but not do the checkout: ~\GitSample\project_vs > git branch example2
As we are still on master, let’s alter one of Foo’s properties by renaming Bar to Bill. Commit that change and switch to the example2 branch. On the example2, rename the Bar property to Gates. Also commit that change and now try to merge the two branches. The workflow in the console would be as such: ~\GitSample\project_vs > git commit -am "Renamed Bar to Bill" [master fa34107] Renamed Bar to Bill 1 file changed, 1 insertion(+), 1 deletion(-)
~\GitSample\project_vs > git checkout example2 Switched to branch 'example2'
~\GitSample\project_vs > git commit -am "Renamed Bar to Gates" [example2 1172efc] Renamed Bar to Gates 1 file changed, 1 insertion(+), 1 deletion(-)
~\GitSample\project_vs > git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 4 commits. (use "git push" to publish your local commits)
~\GitSample\project_vs > git merge example2 Auto-merging Foo.cs CONFLICT (content): Merge conflict in Foo.cs Automatic merge failed; fix conflicts and then commit the result.
You can see Git has no idea which change you would like and it ends up telling us we have conflicts. Now, if we know up front which of the two we would like we could tell Git while using the merge command, otherwise we have to assist Git in selecting the correct changes.
When we have a look at Foo.cs we can see the following: <<<<<<< HEAD public string Bill { get; set; } ======= public string Gates { get; set; } >>>>>>> example2
This is what a merge conflict looks like. Our HEAD (master) has Bill, and Example 2 has Gates as a proposed solution to the problem. It is up to us to tell Git what we need. Remember that with conflicts, Git will create a merge commit that holds the resolved versions of the files. If we try to commit now, Git will simply tell us we have to resolve the conflicts first before we can proceed. VS also knows this so there is no way around it. When you look at Team Explorer, Branches it tells us we have a merge in progress with conflicts and also have the option to abort. Let’s abort, but use the console to do this: ~\GitSample\project_vs > git merge --abort
And just like that all of our worries went away and we can try again. Let’s say we know our changes are correct and want that property to have the name Bill. In that case we could do a merge and accept our changes as the result in case of conflicts. Use the console and enter the following to do so: ~\GitSample\project_vs > git merge example2 -s ours Merge made by the 'ours' strategy.
Git successfully merged the two branches and there are no conflicts. The -s parameter accepts a strategy Git will use to resolve conflicts. Let’s try to do the same in VS. Reset the master branch to the change before the merge commit (git reset HEAD~1 --hard or use VS). Open Team Explorer, Branches and you notice, after selecting merge, there are no options to be found. VS does not yet support this. Let’s see if we can resolve the conflicts manually. Try to merge the example2 branch once again in master using your tool of choice without specifying a strategy. We are left with the conflicts needed to resolve. We could go through them manually by removing the <<<, === and >>> indicators and leaving what we need, or we can use a tool to assist in the process. In the beginning of this guide we changed our merge tool to Meld. To summon Meld and assist us in the process, go to the console and enter the following: ~\GitSample\project_vs > git mergetool --no-prompt
We can now see Meld and 3 files. On the left we have our change in the master branch (local), in the middle is what we have before and on the right is what can be found in example2 (remote). To select a change, press one of the arrow to alter the file in the center. Try adding Gates as the solution, save and close Meld afterwards. When we look at our projectâ&#x20AC;&#x2122;s status using git status we can see Foo.cs is staged with our proposed solution. If we commit this using the console, we can see Git already created a commit message by not specifying one ourselves: ~\GitSample\project_vs > git commit Merge branch 'example2'
# Conflicts: #
Foo.cs
# # It looks like you may be committing a merge. # If this is not correct, please remove the file #
.git/MERGE_HEAD
# and try again.
We can use this or alter it and the change will be committed. Notice that if we did specify a commit message straight from the console, this would have been overwritten. In case you are curious how VS handles this, add VS back as a merge tool to your repositoryâ&#x20AC;&#x2122;s config and rerun the commands.
GIT REBASE Apart from merging, we can also use rebase to join two branches together. The difference is the way it handles integrating changes into the target branch. Imagine you have a very active master branch where everyone keeps merging changes into. Meanwhile you are working on a specific feature that diverted from master a while ago. If everyone has the same workflow, we will see a lot of merge commits in master. While some people will not mind, others will see it as pollution and request a better way of joining two branches. Instead, rebasing will replay your branch on top of the target branch. It will effectively create new commits on top of that branch, creating a perfectly linear history. This also means that if you do this on a public branch like master, it will rewrite history and people will hate you for it. So, if you want to live happily ever after, rebase on non-public branches and you can do a fast forward merge to the target branch. Letâ&#x20AC;&#x2122;s try this using the console first. Remember we created a merge commit by making master run ahead of example. If you still have that commit you can revert to it, otherwise, re-create that very setting. The result we are looking for is a linear history for master by rebasing example on top of master and then merge example into master. You can verify if you have the correct setting bur inning the following command: ~\GitSample\project_vs > git merge example --ff-only fatal: Not possible to fast-forward, aborting.
By telling Git we want to only do a fast forward merge, and thus not create a merge commit, we will have a linear history. But as both master and example diverted from each other we will have to find a way to get example back in line with our beloved master branch. Switch to example and use the git rebase command to replay the changes it has from when it diverted from master on top of the current version of master. ~\GitSample\project_vs > git checkout example Switched to branch 'example'
~\GitSample\project_vs > git rebase master First, rewinding head to replay your work on top of it... Applying: Renamed a property Using index info to reconstruct a base tree... M
Foo.cs
Falling back to patching base and 3-way merge... Auto-merging Foo.cs
You can see Git tells us it replayed the commits from example on top of our master branch. When we switch back to master and ask Git to merge the example branch using fast forward it will gladly do so. ~\GitSample\project_vs > git checkout master Switched to branch 'master'
~\GitSample\project_vs > git merge example --ff-only Updating 71fed85..32073d5 Fast-forward Foo.cs | 2 +1 file changed, 1 insertion(+), 1 deletion(-)
Just like we wanted, Git applied the merge using fast forward. When we look at our history there is no merge commit and everyone will be very pleased with our work. But it gets even more interesting. You can opt for an interactive rebase where you will be able to alter your commits. If you actively worked on a branch, thereâ&#x20AC;&#x2122;s bound to be some commits that should either not be there (image reverted work) or could be renamed (typoâ&#x20AC;&#x2122;s) or joined together (you forgot to amend them). An interactive rebase will allow us to edit the commits before the rebasing begins. Re-create the situation from the previous rebase example where we could not merge example into master using a fast forward. You can create a new branch too, just make sure to use that one instead of example. Now, when we run the rebase command on example, letâ&#x20AC;&#x2122;s add the--interactive option. This will open your text editor and allow you to decide what to do with the commits that need to be replayed. We can see several options: # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # d, drop = remove commit
What they do is explained nicely in that windows. By default, every commit is picked, which means they will get replayed on top of the selected branch as if you did not specify --interactive.
Rewording will allow us to fix typo’s or just badly named commits. Edit will allow us to change wat we did. Squash will join that commit into the previous commit (and use the previous commit’s message. Fixup will also join that commit into the previous commit but you’ll keep the previous commit’s message. Exec will allow you to run a shell command. Drop will remove it.
Note that the commits will be replayed from top to bottom, and if you remove a line you will lose that commit forever. You are after all, rewriting history. VS only has the option to do a regular rebase. You can find it right next to Merge in Team Explorer, Branches. Given that the true power of a rebase and thus a super tidy history lies in the interactive option, I really hope they can add this fast. Otherwise I’ll never leave my comfy console. But it can get even more interesting. What if you want to append changes to a commit that is not your HEAD? We can keep our history super clean using the --squash and --fixup options with the git commit command by selecting the correct commit hash where the changes need to be added: ~\GitSample\project_vs > git commit --fixup bbb2222
Now, we can rebase our own branch by replaying it on top of itself and fix our own history first before doing a rebase with the master branch for example: ~\GitSample\project_vs > git rebase --interactive --autosquash HEAD~3
Once we rebase the commits which were made using the --fixup or --squash option will be marked immediately as such in our Git editor do perform the interactive rebase. A workflow like this will make sure the work you commit is clean and in good shape for anyone to review. If there are changes to be made you can still add them to the right commit afterward instead of creating new commits just to fix issues.
GIT CHERRY-PICK Sometimes you happen to know there’s a fix for a problem you are trying to solve (or saw during development) that’s already son another branch. It would be nice if we could get that very fix and apply it on our branch instead of having to do the same work twice. That’s where git cherry-pick comes into play. If we want to do this from the console, all we need is the commit hash from that specific commit and we are good to go. This is also why your commits have to be complete and logical. If you have to go looking through multiple or even worse, partial commits, you’re a long way from home. Create a commit on the example branch, get the hash and go back to master. You’ll get the following workflow: ~\GitSample\project_vs > git checkout example Switched to branch example
~\GitSample\project_vs > touch file.cs ~\GitSample\project_vs > git add . ~\GitSample\project_vs > commit -am "Added a much needed file" [example 668683f] Added a much needed file 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 file.cs
~\GitSample\project_vs > git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 1 commit. (use "git push" to publish your local commits)
~\GitSample\project_vs > git cherry-pick 668683f [master b467dc7] Added a much needed file Date: Wed Apr 27 12:24:14 2016 +0200 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 file.cs
To illustrate this in VS, undo this commit on master and open VS. Apparently VS shows the option cherry-pick when you right-click a branch in Team Explorer. It’s not really what we are looking for even though it will pick the same commit as it selects the top commit from example and applies that to master. It would be nice if it would allow us to select the commit on that branch. We can however find what we are looking for in the branch history. Right-click the example branch and select View History. Right-click the commit you want and press Cherry-Pick. If all went well you will see that commit on top of your master branch.
GIT TAG Git allows us to add tags to a commit. In most use cases (mostly the only one though) it is used to indicate a specific version of the code. There are two types of tags you can use, lightweight and annotated. The difference is the way they are stored. A lightweight tag is a pointer to a commit much like a branch whereas an annotated tag is stored as an object in Git’s database. Annotated tags are checksummed; contain the tagger name, email, and date; have a tagging message; and can be signed and verified with GNU Privacy Guard (GPG). It’s generally recommended that you create annotated tags so you can have all this information; but if you want a temporary tag or for some reason don’t want to keep the other information, lightweight tags are available too. To tag a commit, use the following syntax from the console: ~\GitSample\project_vs > git tag -a v1 -m "version 1" ~\GitSample\project_vs > git history * 71fed85 -
(HEAD -> master, tag: v1) Added the cafe property (17 hours ago) <Jan De Dobbeleer>
* fd65d20 -
(origin/master) Added property to Foo (2 days ago) <Jan De Dobbeleer>
* 321a31d -
Renamed the last commit (5 days ago) <Jan De Dobbeleer>
* fa657a5 -
Initial commit for Foo (7 days ago) <Jan De Dobbeleer>
* e098bbf -
Add project files. (9 days ago) <Jan De Dobbeleer>
* deed032 -
Add .gitignore and .gitattributes. (9 days ago) <Jan De Dobbeleer>
You can see Git’s logs output the tags just like a branch to a commit. If we want to add a tag to a commit other than the last one, specify the commit hash at the end: ~\GitSample\project_vs > git tag -a a1 -m "alpha 1" 321a31d ~\GitSample\project_vs > git history * 71fed85 -
(HEAD -> master, tag: v1) Added the cafe property (17 hours ago) <Jan De Dobbeleer>
* fd65d20 -
(origin/master) Added property to Foo (2 days ago) <Jan De Dobbeleer>
* 321a31d -
(tag: a1) Renamed the last commit (5 days ago) <Jan De Dobbeleer>
* fa657a5 -
Initial commit for Foo (7 days ago) <Jan De Dobbeleer>
* e098bbf -
Add project files. (9 days ago) <Jan De Dobbeleer>
* deed032 -
Add .gitignore and .gitattributes. (9 days ago) <Jan De Dobbeleer>
To list all the tags, just type git tag: ~\GitSample\project_vs > git tag a1 v1
Be advised that Git does not push your locally created tags to the remote unless you specify this. Use the git push origin --tags command to push them all at once. Just like branches you can checkout a tag, this makes them extremely handy when you use them for storing version numbers. That way you can quickly go back to a version if needed, or compare between versions. You can also run git diff a1 v1 to see what changed between those versions too in case of troubleshooting. To add a tag from VS, go to a branchâ&#x20AC;&#x2122;s history, right-click the commit and select Create Tag. This opens the commit detail windows where you can add the needed information. VS will also create annotated tags so we donâ&#x20AC;&#x2122;t have to bother to check for that. If you need to delete a tag your only option seems to be the console for now. Just use the -d option with the git tag command: ~\GitSample\project_vs > git tag -d v0.5 Deleted tag 'v0.5' (was fd65d20)
And if you have to remove the tag from the remote too, push that information to it: ~\GitSample\project_vs > git push origin :refs/tags/v0.5
GIT STASH Often, when youâ&#x20AC;&#x2122;re working on something and have to switch to another branch to quickly do something else, you do not want to commit half-done work. So you need a way to put those changes aside and get back to this point later. The answer to this issue is the git stash command. ~\GitSample\project_vs > git status On branch master Your branch is ahead of 'origin/master' by 1 commit. (use "git push" to publish your local commits) Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified:
Foo.cs
no changes added to commit (use "git add" and/or "git commit -a")
~\GitSample\project_vs > git stash Saved working directory and index state WIP on master: 71fed85 Added the cafe property HEAD is now at 71fed85 Added the cafe property
If we want to see what we have stashed, just use the list parameter to show them. If we feel like adding the changes back to our working tree, use the pop parameter with the correct index (defaults to 1) to pop them back. ~\GitSample\project_vs > git stash list stash@{0}: WIP on master: 71fed85 Added the cafe property
~\GitSample\project_vs > git stash pop "stash@{0}" On branch master Your branch is ahead of 'origin/master' by 1 commit. (use "git push" to publish your local commits) Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified:
Foo.cs
no changes added to commit (use "git add" and/or "git commit -a") Dropped stash@{0} (08b256c78dfbd59645181c05e27c8ba3f3f9425f)
The pop command is actually a combination of two command within stash. First, Git will try to apply the changes by using git stash apply. If it applied successfully, it can then use git stash drop to remove the stash from the list. Note that you can apply a stash on a dirty working tree and merge conflicts can arise. The stash option is not yet available in VS, letâ&#x20AC;&#x2122;s hope the team can quickly add it is it is a heavily used Git feature.
GIT WORKTREE The downside of stashing is that you can’t stash untracked files. So you will have to remove them or commit temporarily. But as those options are not what you want (but still might do) there is an alternative. Git has the option to add more than one work tree. What it means is you can have extra folders where you share the files and branches but not the notion of HEAD and INDEX. Git creates a work tree when you use git clone or init (except when using the --bare option). That way you can start working immediately. VS does not yet allow manipulation of work trees so let’s have a look at the current work trees in the console.: ~\GitSample\project_vs > git worktree list C:/Users/Jan/GitSample/project_vs
71fed85 [master]
Git tells us we have one work tree and indicates the folder where it is located. By default it’s the folder where we can find the .git folder. Hence why a bare repository does not have a .git folder and thus no working directory. To add another work tree, we have to tell Git where to create it and which branch we want to check out. Be advised that you can’t normally create a new work tree with the branch you are currently working on (force it with the --force option). So, choose another branch or create a new one from the branch you are currently on.
You can create a work tree anywhere; it does not need to be a subfolder inside your repository. Add a file to project_vs so that we can simulate having 1 untracked file in our repository. You can try to stash it, but as you will notice, it’s still there afterwards. Let’s create a new work tree called project_vs2 in the ~\GitSample folder and see if we can get rid of it there. ~\GitSample\project_vs > touch untracked.txt ~\GitSample\project_vs > git worktree add -b feature/fixBug C:\Users\Jan\GitSample\project_vs2 master Preparing C:\Users\Jan\GitSample\project_vs2 (identifier project_vs2) HEAD is now at 71fed85 Added the cafe property
So, what we did is create a folder called project_vs2 that holds our second work tree including a new branch called feature/fixBug. When we open that folder in Explorer we can see it holds our repository files and a .git file that is a reference towards the repository’s actual .git folder.
Having a look in that folder using the console, we can see the branch we created and no untracked.txt file. ~\GitSample\project_vs > git cd C:\Users\Jan\GitSample\project_vs2\ ~\GitSample\project_vs2 > git git status On branch feature/fixBug nothing to commit, working directory clean
Git stores our work trees in the .git folder. When we have a look now, we see a new folder called worktrees. For each work tree, we have a subfolder where we can see the HEAD, index, ORIG_HEAD, the location of the git directory and common folders. When we look at which work trees we have now using the console, we get the following list: ~\GitSample\project_vs2 > git worktree list C:/Users/Jan/GitSample/project_vs
71fed85 [master]
C:/Users/Jan/GitSample/project_vs2
71fed85 [feature/fixBug]
To remove the work tree, delete the project_vs2 folder and use git worktree prune to update the list we have: ~\GitSample\project_vs > rm ~\GitSample\project_vs2\ -Recurse -Force ~\GitSample\project_vs2 > git worktree prune ~\GitSample\project_vs2 > git worktree list C:/Users/Jan/GitSample/project_vs
71fed85 [master]
And just like that we are back to having 1 work tree. The worktrees folder inside the .git folder is still available, but as you can see itâ&#x20AC;&#x2122;s empty.
HIDDEN GEMS If you want to see what changed over a period of time you can use the --stat option on the diff command. It accepts a string where you can specify the timeframe. ~\GitSample\project_vs > git diff --stat "@{9.days.ago}..@{now}" warning: Log for 'master' only goes back to Mon, 18 Apr 2016 12:57:05 +0200. App.config
|
6 +++++
ConsoleApplication1.csproj | 61 ++++++++++++++++++++++++++++++++++++++++++++++ ConsoleApplication1.sln
| 22 +++++++++++++++++
Foo.cs
| 17 +++++++++++++
Program.cs
| 16 ++++++++++++
Properties/AssemblyInfo.cs | 36 +++++++++++++++++++++++++++ 6 files changed, 158 insertions(+)
There is a secret setting that will allow you to have autocorrect in Git. ~\GitSample\project_vs > git dff git: 'dfff' is not a git command. See 'git --help'.
Did you mean this? diff
~\GitSample\project_vs > git config --global help.autocorrect 1 ~\GitSample\project_vs > git dff WARNING: You called a Git command named 'dff', which does not exist. Continuing under the assumption that you meant 'diff' in 0.1 seconds automatically...
You can even have a list off all objects that arenâ&#x20AC;&#x2122;t referenced by anything else, lost commits per se. ~\GitSample\project_vs > git fsck â&#x20AC;&#x201C;full
In case you would love to only see what changed instead of having Git highlight the entire line when viewing a diff, use the --word-diff option when running a diff. ~\GitSample\project_vs > git diff --word-diff master example diff --git a/Foo.cs b/Foo.cs index 519d2ad..0891674 100644 --- a/Foo.cs +++ b/Foo.cs @@ -9,8 +9,8 @@ namespace ConsoleApplication1 { class Foo { public string [-Bar-]{+Bar6+} { get; set; } public string [-Bar2-]{+Bar4+} { get; set; } public string Bar3 { get; set; } public string Cafe { get; set; } }
You can use a short status if you feel there is too much information on your screen: ~\GitSample\project_vs > git status On branch master Your branch is ahead of 'origin/master' by 1 commit. (use "git push" to publish your local commits) Untracked files: (use "git add <file>..." to include in what will be committed)
file.cs
nothing added to commit but untracked files present (use "git add" to track)
~\GitSample\project_vs > git status -sb ## master...origin/master [ahead 1] ?? file.cs