This section contains some of the preparation work before being able to start contributing to the repository.
The first and more important thing to know is that:
"Git is a distributed version-control system for tracking changes in source code during software development" - Wikipedia
Pay special attention to the highlighted word: distributed because Git is all about having multiple copies of the code (along with the history of changes) in different locations:
- Your local development environment
- The company Github account
- Your Github account
- ...
- To have a Github account (can be your personal account)
- Have the account configured to work with SSH †
- Have a terminal to work on
- Have Git installed
- Have Git configured:
git config --global user.name "Firstname Lastname"
git config --global user.email "[email protected]"
† Configuring SSH is beyond the scope of this document but Github has great documentation about this here.
In this section you will be doing the preparation work before you can start contributing to this repository.
The copy of this repository that lives in the BeenVerifiedInc account is
what will be called canonical
throughout this document.
The first step is to create a fork of it on your Github account. A fork is the Github term for a copy of a repository. Forking allows you to experiment without affecting the canonical project.
Because it is a Github term (not a Git term), forking has to be done directly in the Github UI:
(screenshot - PENDING)
Now that you have a fork of this repository, it is time to clone it to your local environment (e.g. your laptop).
$ git clone [email protected]:<your github username>/git-training.git
At this point you are aware of at least 3 copies:
canonical
in Github- Your fork in Github
- Your local copy
When you cloned from your fork, Git did a few things for you:
- Created a directory
- Downloaded all the code
- Downloaded repository meta information (stored in a
.git
directory)
As part of the meta information, it also created what's called a remote.
Remote repositories are versions of your project that are hosted on the Internet or network somewhere. - Git Book
Remotes are places where you can pull code from or push code to.
In order to display existing remotes, you can use the following command:
git remote -v
The clone action also made some deliberate decisions for you, like naming this
first remote origin
and downloading the main branch of the remote repository
which in almost every case is called master
.
The status of the repository shows it:
git status
As part of downloading the main branch, it had to create one locally and also the respective remote one.
Check it out:
git branch --all
The above branch command can also be run without options to display only the local ones:
git branch
Or with the -r
or --remote
option to display only the remote ones:
git branch --remote
At this moment your master
branch is probably sync-ed with canonical's
master
but at some point in the future that will no longer be the case.
Specially when there is more than one person contributing to the same
repository; imagine fellow developers adding changes to that master
while you
are still working on your feature.
Each time you start to work on something new, it is ideal that you add your
work on top of the most recent version of canonical master
that there is.
Therefore, before doing any new work, you need to pull from that master
.
For being able to pull from it, it first needs to be added as a remote:
git remote add canonical [email protected]:BeenVerifiedInc/git-training.git
When you list your remotes once again, the new remote shows up:
git remote -v
You can add more remotes, like each of your co-worker's forks. And the name you
give to them is picked by you. When adding the BeenVerifiedInc
, it was added
with the name "canonical" just as a convention used within BeenVerified so that
when one person suggests to another to run a command that involves using this
remote, both can run the exact same command without having to adapt it. It is
similar to how Git decided to name your fork "origin."
Thus, we currently have:
- origin = your fork
- canonical = BeenVerifiedInc's
As it was mentioned earlier, in order to minimize merge conflicts, new work
needs to be based from the most recent version of canonical master
.
There are two ways of retrieving it.
When you cloned from your fork, it left your local copy set on the master branch
and its upstream set to point to origin
(i.e. your fork). This can be
changed, but at the moment it is not required.
The first thing you are going to do is fetch all of what has changed in the canonical remote:
git fetch canonical
This updates all your canonical remote branches, all those named
canonical/<branch name>
, including canonical/master
. What this means is it
downloads the content of all those branches from Github's BeenVerifiedInc copy
of the repository and stores them in the .git
directory of your local copy.
Making them available for you to use.
The next thing to do is merge the latest changes of that remote master
branch into your master
:
git merge canonical/master
If you always work with your local master
branch in an organized way and you
and your team mates are disciplined when working with the canonical master
branch (i.e. not altering it's history), you should always be able to do the
above merge without issues.
But if when trying to merge, there are conflicts, then you can instead
reset your local master
to have exactly the same history that the remote
one has but keeps all changes, just uncommitted.
git reset canonical/master
If your local master has changes that were added by mistake and can or should be discarded, you can do a hard reset.
git reset --hard canonical/master
Just beware that reset gets rid of all the local commits that you had. It
overwrites the history and leaves you with the code and history identical to the
branch you are reseting from, in this case canonical/master
.
This information may be a little overwhelming at this point but reset and changes to the history will be covered in more detail later on.
The pull command combines fetch and merge in one, but making use of the preconfigured upstream of your local branch.
Currently your upstream is set to origin master
, then you need to instruct
Git to use another one. We do this by using the -u
flag when invoking the
pull command †.
git pull -u canonical master
The above command is equivalent to:
git fetch canonical
git merge canonical/master
As you can see, pull is more convenient but less flexible. It doesn't provide the option of doing a reset or a hard reset in case the merge doesn't happen smoothly. For this reason it is important to understand both options.
† The upstream can be set with -u
in the same way when using the push
command.
Branches have been mentioned a lot throughout this guide but without providing much information of what their meaning is in Git.
Think of them as "parallel universes". Each with their own timeline.
Going back to the definition of Git, it is "a version-control system". It keeps track of all the changes made to files in a repository; it does so by taking snapshots of the repository at different points in time. These snapshots are called commits.
The full set of commits make the history of the repository. Continuing with the analogy, let's say the commit history is the universe's timeline.
But Git is a "multiverse" and it contains multiple copies of a repository (multiple universes), each having it's own timeline.
New "parallel universes" can branch out from a pre-existing one. The new one shares the history of its originator up to the instant it spawned, beyond this point, it starts to have its own history.
Visually explained:
master: 0--1--2--3--4--5--
\
new branch: `--3'--4'--
They share the same history until 2, but then the new branch becomes an "alternate reality" of its originator.
Before starting to work on a new feature or bug-fix, it is a good practice to create a branch with a descriptive name for it:
git checkout -b sign-guestbook
The git checkout
command allows you to bring up another branch, but when
passing it the -b
flag, it creates a new branch. And that is exactly what
you just did.
So you just created a new branch and pulled it out.
Remember you can always check at which branch you are at with the git status
command. Please run it and make sure you are at the newly created branch.
This repository has a guestbook/
directory that keeps track of every person
that has fulfilled this exercise.
The first change that you will add to this repository is creating a file with
your name to it. The name of the file will be as your user name in your
BeenVerified email address and the .md
extension.
cd guestbook
touch <username>.md
Check the status of the repository:
git status
It informs of the new file added.
In your favorite editor, let's open the newly created file and add the following text:
- [X] Create my guestbook file
- [X] Check the git status
- [X] Add text to it
- [ ] See the diff when only new files are added
- [ ] Modify file after it was staged
- [ ] Check the status when part of the file's changes is staged
- [ ] See the diff when a file is partially staged
- [ ] Add the rest of the changes
- [ ] Create my first commit
- [ ] Create my second commit
Save it and run:
git diff
Shows nothing. Although there are changes, it is only a new file (utracked), so there's nothing to compare it against. The file has never been tracked yet.
Then run:
git status
Again nothing new because the file isn't tracked, so to Git the change is that there is a new file, it doesn't know anything about its contents changes from the moment it was created to the moment it was modified.
Update the file to mark this dull step as done:
- [X] Create my guestbook file
- [X] Check the git status
- [X] Add text to it
- [X] See the diff when only new files are added (it is empty)
- [ ] Modify file after it was staged
- [ ] Check the status when part of the file's changes is staged
- [ ] See the diff when a file is partially staged
- [ ] Add the rest of the changes
- [ ] Create my first commit
- [ ] Create my second commit
Git provides a staging area which is very well explained here (in the first two paragraphs).
So instead of just creating a commit straight from what you just changed in the repository (add a new file), the change needs to be staged first:
git add <username>.md
Check the status again:
git status
It now tells us that we have staged changes or "Changes to be committed".
We can check what changed from the original status of the repository to what is
staged with git diff
but with the --staged
flag:
git diff --staged
In this case it does inform us that there is a new file.
Let's modify the file again by marking that task as done.
- [X] Create my guestbook file
- [X] Check the git status
- [X] Add text to it
- [X] See the diff when only new files are added (it is empty)
- [X] Modify file after it was staged
- [ ] Check the status when part of the file's changes is staged
- [ ] See the diff when a file is partially staged
- [ ] Add the rest of the changes
- [ ] Create my first commit
- [ ] Create my second commit
And check the diff again:
git diff
It now shows what was recently added.
If we check the status:
git status
It lists the same file in both sections: staged and not staged.
Let's mark four more tasks as completed.
- [X] Create my guestbook file
- [X] Check the git status
- [X] Add text to it
- [X] See the diff when only new files are added (it is empty)
- [X] Modify file after it was staged
- [X] Check the status when part of the file's changes is staged
- [X] See the diff when a file is partially staged
- [X] Add the rest of the changes
- [X] Create my first commit
- [ ] Create my second commit
The commands for the latter two tasks:
git add <username>.md
git commit -m "Add <username>.md file to guestbook directory"
Congratulations! You just started altering the new alternate reality that you spawned.
Your commit is now part of this new history and this can be corroborated by running:
git log
When running git status
, your working tree should show as "clean" again.
By now you should already have realized that git status
and git diff
are
your friends. You can run those commands as many times you need in order to be
sure you are adding and committing what you intended to.
You just created a commit with a message that describes what it changed in the repository. The message could have said whatever you wanted to, even nonsense and it could have had any format like all caps or all lowercase, but you need to always keep in mind that commit messages are a mean of written communication between team members and as such, it requires to have good qualities:
- Informative
- Succint
- Professional grammar, punctuation and spelling
Git commit messages are composed of a subject and a body, just like email messages. In fact this is how some teams distribute changes: by exchanging patches through email †.
For setting the message of the commit that you just created, you used the -m
option flag which allows you to write the message right there.
Because you only provided one -m
then the message only had one paragraph,
but it could have had more than one.
git commit -m "First paragraph (subject)" -m "Second paragraph (part of the body)"
Git takes the first paragraph as the subject and the rest of them as the body of the message.
But for when you want to include a body in your commit message, Git offers a
more convenient way of doing so by calling commit
without the -m
option
flag.
git commit
It will launch your predefined editor so you can write the full message in there and after you save and close the document, the commit is created.
The predefined editor can be set with:
git config --global core.editor vim
Going back to the format, here's a model commit message courtesy of Tim Pope and also used in Git's book:
Capitalized, short (50 chars or less) summary
More detailed explanatory text, if necessary. Wrap it to about 72
characters or so. In some contexts, the first line is treated as the
subject of an email and the rest of the text as the body. The blank
line separating the summary from the body is critical (unless you omit
the body entirely); tools like rebase can get confused if you run the
two together.
Write your commit message in the imperative: "Fix bug" and not "Fixed bug"
or "Fixes bug." This convention matches up with commit messages generated
by commands like git merge and git revert.
Further paragraphs come after blank lines.
- Bullet points are okay, too
- Typically a hyphen or asterisk is used for the bullet, followed by a
single space, with blank lines in between, but conventions vary here
- Use a hanging indent
† http://alblue.bandlem.com/2011/12/git-tip-of-week-patches-by-email.html
It is expected to:
- Inform about what changed.
- Start with capital letter.
- Be 50 or less characters long.
- Start with imperative (to match Git's auto-generated commit messages).
- Don't end with a period.
It should not contain a ticket number or a "look elsewhere" reference because:
- Takes valuable space from the subject line.
- A ticket number does not express what changed by itself, it adds a dependency.
It should mostly express the why of the change because the what is summarized in the subject and can also be seen directly in the code changes.
It can contain the "look elsewhere" reference because space isn't limited and a ticket may help to add information an context about the why. Some teams have the convention of specifying the ticket number in its own paragraph, the very last paragraph. This is specially useful when the tracker tool has the ability of reading this information and automatically creating a hyper-link.
The body isn't always necessary, specially in the case of small changes that
don't require much justification or explanation. In such cases it is faster to
just use the -m
flag.
Let's open the document again and mark the last task as done.
- [X] Create my guestbook file
- [X] Check the git status
- [X] Add text to it
- [X] See the diff when only new files are added (it is empty)
- [X] Modify file after it was staged
- [X] Check the status when part of the file's changes is staged
- [X] See the diff when a file is partially staged
- [X] Add the rest of the changes
- [X] Create my first commit
- [X] Create my second commit
Then add and commit:
git add <username>.md
git status
git commit -m "Mark last task of <username>.md as completed"
You can check the log once again to corroborate.
At this point, you have signed the guestbook but only in your local copy of your "alternate reality". It is a good idea to frequently back-up your changes and doing so is just one command away:
git push origin sign-guestbook
What push does is take your current local branch sign-guestbook
and merges
it into the remote branch that you specify to it. You specify which remote
branch by telling it the name of the remote and the name of the branch.
If the remote branch doesn't exist yet, it creates it.
There is also a -u
flag that you can use to tell it to mark that remote branch
as the default upstream for current local one.
git push -u origin sign-guestbook
Then, going forward, you don't need to specify the remote branch when doing a push or pull from this branch.
Now that you have an alternate reality with two "milestones" that aren't part of the history of the original timeline (canonical), but your timeline went so well that you want that new history to be part of the original one.
Then you request the owner or administrator of the canonical one to pull from your branch into theirs.
Curious fact: Git has a command request-pull
that produces a written
message that you can send to someone else inviting them to use git pull
. So
the concept of a pull request is as simple as that but nowadays people
associate it more with GitHub's sophisticated UI feature.
For such reason, Git is now kind and smart enough to provide you a link for creating a pull request in Github the first time that you push your local branch to a remote (only if your remote has a github.com address).
Then let's take advantage of that and use the link for creating your first one.
Into which branch do you want the administrator of canonical to merge your changes.
You need to specify which remote (Github respository:
BeenVerifiedInc/) and the branch name (usually master
).
Which branch is the one that you want to merge.
You need to specify which remote (Github respository: /) and the branch name.
Unless you tell Github to rebase upon accepting the pull request, it merges and forces the creation of a merge commit. Such commit follows similar rules and structure of normal commits presented earlier in this training.
The idea of the pull request is to act as a control gate and allow other team members to review the changes before incorporating them into the main banch of the project.
Github offers advanced UI tools for helping in the review process, like the ability to write comments about sets of line(s) of code, request modifications or finally approve and merge the changes.
After all is good and the pull request is merged, then the ticket can be considered code complete.
PENDING
It is important to understand why this happens. Git is smart enough to solve simple conflicts but sometimes it needs human input to make a call. This commonly happens when the exact same lines were edited in a file. Git has no context, therefore it cannot know which change is the correct one. Here is where the developer input is needed.
While checking out a branch:
- Current changes in your current working directory / staging area could be conflicting with the state of a branch. This commonly happens when you made a change to a file and then you try to checkout a different branch (like staging) where that file also was changed. (Changes should be on the same lines for this to happen, otherwise git auto merges both files and leaves it as unstaged)
While trying to merge with a branch:
- A failure DURING a merge indicates a conflict between the current local branch and the branch being merged. This indicates a conflict with another developer's code. Git will do its best to merge the files but will leave things for you to resolve manually in the conflicted files.
Some things to keep in mind:
- First and foremost: Keep calm 🧘
- The process of resolving/merging a conflict is totally normal and can be
undone at any moment (don't panic):
$ git merge --abort
- Conflicts are just annotations or marks of where in a file a conflict is present
- Git denotes conflicts by using the following symbols:
<<<<<<< HEAD
1,=======
,>>>>>>> [branch name]
- You can use
$ git status
to know which files are marked as conflicting - When a conflict occurs, you open the concerned file with your favorite text editor and resolve said conflict from there
Note: You can also use specialized tools for handling conflicts (links below). Some text editors / IDEs also have build-in tools for solving conflicts but for learning purposes, we will continue to use git commands to achieve this.
Sadly, no. But you minimize the impact to some degree if you fetch remote changes frequently from the main branches and then handle the changes upstream. While you may need to resolve merge conflicts more frequently, this means that you'll be resolving smaller conflicts each time albeit a lot easier to resolve. Also you will be more aware of other changes that are ocurring to the codebase.
Now a simple example of how to solve a conflict (merging branches)
First, let's create a conflict!
Using the current branch where this file is in (sign-guestbook
), let's create
and checkout a new branch and add some new content on this README.
$ git checkout -b conflict_maker
Then using your text editor of choice, update the title of the README.md file to "Git Conflict" and run the following.
$ git commit -am "Update title of README.md"
[conflict_maker 123456] Update title of README.md
1 file changed, 1 insertion(+), 1 deletion(-)
Current checklist:
+ [X] Create a new 'conflict_maker' branch
+ [X] Update the README.md file with a new title
+ [X] Commit change to title
We will now use this branch to generate a conflict with the base branch
(sign-guestbook
) but first, let's go back to our base branch and add some new
content to the README.
The following commands will create a new commit in sign-guestbook
and will put
the repo in a new state with 2 commits
$ git checkout sign-guestbook
Switched to branch 'sign-guestbook'
Let's update the title of the README.md file to "Git Exercise 2.0" and run the following.
$ git commit -am "Add new title to README.md"
[sign-guestbook 123abc] Add new title to README.md
1 file changed, 1 insertion(+)
So far:
[X] Create a new 'conflict_maker' branch
[X] Update the README.md file with a new title
[X] Commit change to title
+ [X] Checkout to 'sign-guestbook'
+ [X] Add a new title to README.md
+ [X] Commit change to title
Now, if we try to merge the new branch with sign-guestbook
we will get...
$ git merge conflict_maker
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
The often times dreaded CONFLICT message, but that's ok. We can solve this conflict very easily. First, let's start by doing a quick status check
$ git status
On branch sign-guestbook
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: README.md
Current checklist:
[X] Create a new 'conflict_maker' branch
[X] Update the README.md file with a new title
[X] Commit change to title
[X] Checkout to 'sign-guestbook'
[X] Add a new title to README.md
[X] Commit change to title
+ [X] Run git merge and check the status of the merge (Conflict!)
+ [X] Check status of merge
The current status shows that there are unmerged paths due to a conflict. README.md is clearly marked as modified and needs to be rectified Now using your favorite editor, open the README.md file to see what the conflict is all about. You should see something like this near the title section
<<<<<<< HEAD
Git Exercise 2.0
=======
Git Conflict
>>>>>>> conflict_maker
Like noted before, conflicts are marked by Git by using the
<<<<<<<
, =======
, >>>>>>>
dividers. The =====
represents the center of
the conflict, the first section denoted by <<<<<<<
and =======
will be the
content of the current branch you are on (commonly refered as HEAD).
Likewise whatever is after the =======
but before the >>>>>>>
will be the
new merging branch content.
Solving this conflict should be pretty straightforward as you just need to delete the version you don't want to keep (for this case let's drop the "Git Conflict" title)
- <<<<<<< HEAD
+ Git Exercise 2.0
- =======
- Git Conflict
- >>>>>>> conflict_maker
Once the file is correct, stage and commit the changes.
$ git commit -m "Merge and resolve the conflict in README.md"
Wrapping up:
[X] Create a new 'conflict_maker' branch
[X] Update the README.md file with a new title
[X] Commit change to title
[X] Checkout to 'sign-guestbook'
[X] Add a new title to README.md
[X] Commit change to title
[X] Run git merge and check the status of the merge (Conflict!)
[X] Check status of merge
+ [X] Resolve conflict between files
+ [X] Commit the new conflict-free README.md
That's it! One less conflict to worry about. 🕊️
PENDING
PENDING
Sometimes you start working on a branch but between you started it and the moment it is ready for merging with master, somebody else updated that master so you need to rebase.
PENDING
PENDING
PENDING
- branch
- canonical
- commit
- HEAD
- remote
- upstream
Footnotes
-
HEAD is a reference to the last commit in the currently check-out branch. ↩