On MacOS we will use Homebrew to manage the installation of most development related packages. Homebrew (or simply brew
) is a package manager that lets you install a wide range of open source and binary packages in a consistent way.
Get brew
installed with a curl|bash
. Open your terminal and use the command below to run the installer.
> /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
You can check out the detailed installation instructions on the Homebrew website. That's also where you'll want to go to discover packages and get help.
For now we'll really only be using the install
command.
Now that Homebrew is installed, use that to get Git installed.
> brew install git
You can check that git is working properly by executing it with the --version
flag.
> git --version
Next: LAB-02: Github setup
Head to github.com and sign up for a new account with your work email address. Select a username that you wouldn't mind a client seeing (though, that's pretty unlikely). Use a secure password generated by your password manager of choice.
If you have an active Github presence then you should feel free to add a link to your personal account in the descirption for your new work account.
Setting up a new account for work is a great example of how we want to keep your personal and work accounts separate.
It's good practice to use MFA for all sites that have it available. This is critically important when our confidential information is at play.
Head to your account settings and set up your mobile device or hardware token as a second factor. You should already have Microsoft Authenticator installed to use as a second factor for your work email account.
If you already have Authy, Google Authenticator or another TOTP application installed then feel free to use that instead.
Follow Github's detailed instructions for generating and adding a new SSH key to your account.
Send a quick message to one of today's hosts to be added to the Github organisation. Once you're there you'll be able to see some of our internal projects (including the respository for this workshop!)
Next: LAB-03: Configure Git
The .gitconfig
file in your home directory lets you control global configuration for git. You can edit the file manually, or use git config
on the command line to modify it.
Start by setting your name and email address:
> git config user.email <your-work-email>
> git config user.name <your-full-name>
You can use cat
to display the output of your .gitconfig
file:
> cat ~/.gitconfig
Next: LAB-04: Create a Project
Let's start by creating an empty directory named “hello”.
> mkdir hello
> cd hello
Then add the following code to a new file called hello.rb
:
puts "Hello, World"
It's optional, but at this point you can execute this file with the ruby
interpreter to see the output.
> ruby hello.rb
Congratulations, you're a ruby development now :D.
You now have a directory with a single file. To create a git repository from that directory, run the git init command.
> git init
> git init
Initialized empty Git repository in /Users/jim/Downloads/git_tutorial/work/hello/.git/
Now let’s add the “Hello, World” program to the repository.
> git add hello.rb
> git commit -m "First Commit"
> git add hello.rb
> git commit -m "First Commit"
[main (root-commit) 19f3881] First Commit
1 file changed, 1 insertion(+)
create mode 100644 hello.rb
Next: LAB-05: Checking Status
Use the git status
command to check the current status of the repository.
> git status
> git status
On branch main
nothing to commit, working tree clean
The status command reports that there is nothing to commit. This means that the repository has all the current state of the working directory. There are no outstanding changes to record.
We will use the git status
command to continue to monitor the state between the repository and the working directory.
Next: LAB-06: Making Changes
It’s time to change our hello program to take an argument from the command line. Change the file to be:
puts "Hello, #{ARGV.first}!"
Now check the status of the working directory.
> git status
> git status
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: hello.rb
no changes added to commit (use "git add" and/or "git commit -a")
The first thing to notice is that git knows that the hello.rb
file has been modified, but git has not yet been notified of these changes.
Also notice that the status message gives you hints about what you need to do next. If you want to add these changes to the repository, then use the git add
command. Otherwise the git restore
command can be used to discard the changes.
Next: LAB-07: Staging Changes
Now tell git to stage the changes. Then, check the status.
> git add hello.rb
> git status
You should see:
> git add hello.rb
> git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: hello.rb
The change to the hello.rb
file has been staged. This means that git now knows about the change, but the change hasn’t been permanently recorded in the repository yet. The next commit operation will include the staged changes.
If you decide you don’t want to commit that change after all, the status command reminds you that the git restore
command can be used to unstage that change.
A separate staging step in git is in line with the philosophy of getting out of the way until you need to deal with source control. You can continue to make changes to your working directory, and then at the point you want to interact with source control, git allows you to record your changes in small commits that record exactly what you did.
For example, suppose you edited three files (a.rb
, b.rb
, and c.rb
). Now you want to commit all the changes, but you want the changes in a.rb
and b.rb
to be a single commit, while the changes to c.rb
are not logically related to the first two files and should be a separate commit.
You could do the following:
> git add a.rb
> git add b.rb
> git commit -m "Changes for a and b"
git add c.rb
git commit -m "Unrelated change to c"
By separating staging and committing, you have the ability to easily fine tune what goes into each commit.
Ok, enough about staging. Let’s commit what we have staged to the repository.
When you used git commit
previously to commit the initial version of the hello.rb
file to the repository, you included the -m
flag that gave a comment on the command line. The commit command will allow you to interactively edit a comment for the commit. Let’s try that now.
If you omit the -m
flag from the command line, git will pop you into the editor of your choice. The editor is chosen from the following list (in priority order):
I have the EDITOR variable set to nano
.
So commit now and check the status.
> git commit
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch main
# Changes to be committed:
# modified: hello.rb
#
On the first line, enter the comment: “Using ARGV”. Save the file and exit the editor. You should see output similar to the following.
> git commit
[main 2eac0b2] Using ARGV
1 file changed, 1 insertion(+), 1 deletion(-)
Finally, let's check the status again.
> git status
> git status
On branch main
nothing to commit, working tree clean
The working directory is clean and ready for you to continue.
Most source control systems work with files. You add a file to source control and the system will track changes to the file from that point on.
Git focuses on the changes to a file rather than the file itself. When you say git add file, you are not telling git to add the file to the repository. Rather you are saying that git should make note of the current state of that file to be committed later.
We will attempt to explore that difference in this lab.
Change the “Hello, World” program to have a default value if a command line argument is not supplied.
name = ARGV.first || "World"
puts "Hello, #{name}!"
Now add this change to git's staging area.
> git add hello.rb
Now add a comment to the “Hello, World” program.
# Default is "World"
name = ARGV.first || "World"
puts "Hello, #{name}!"
> git status
You should see:
git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: hello.rb
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: hello.rb
Notice how hello.rb
is listed twice in the status. The first change (adding a default) is staged and is ready to be committed. The second change (adding a comment) is unstaged. If you were to commit right now, the comment would not be saved in the repository.
Let’s try that.
Commit the staged change (the default value), and then recheck the status.
> git commit -m "Added a default value"
> git status
You should see:
> git commit -m "Added a default value"
[main 58498b1] Added a default value
1 file changed, 3 insertions(+), 1 deletion(-)
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: hello.rb
no changes added to commit (use "git add" and/or "git commit -a")
The status command is telling you that hello.rb
has unrecorded changes, but is no longer in the staging area.
Now add the second change to the staging area, then run git status
.
> git add .
> git status
We just used the current directory (‘.’) as the file to add. This is a really convenient shortcut for adding in all the changes to the files in the current directory and below. But since it adds everything, it is a really good idea to check the status before doing an add .
, just to make sure you don’t add any file that is not intended.
You should see:
> git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: hello.rb
Now the second change has been staged and is ready to commit.
> git commit -m "Added a comment"
Next: LAB-11: History
Getting a listing of what changes have been made is the function of the git log command.
> git log
You should see:
> git log
commit c51cd709b284ba82ae2c7d1578e242cd11dc8a0e
Author: Git Committer <me@example.org>
Date: Mon Aug 15 14:54:42 2022 +1000
Added a comment
commit 58498b15c685b8332eb2031764cb2fd80e18a886
Author: Git Committer <me@example.org>
Date: Mon Aug 15 14:54:42 2022 +1000
Added a default value
commit a03ff84b97f40549ea1e21c4680a2fd3f30eeec4
Author: Git Committer <me@example.org>
Date: Mon Aug 15 14:54:42 2022 +1000
Using ARGV
commit 19f3881f40ceb5fa20328bf94a749b2c2ba650d7
Author: Git Committer <me@example.org>
Date: Mon Aug 15 14:54:42 2022 +1000
First Commit
You have a great deal of control over exactly what the log
command displays. I like the one line format:
> git log --pretty=oneline
> git log --pretty=oneline
c51cd709b284ba82ae2c7d1578e242cd11dc8a0e Added a comment
58498b15c685b8332eb2031764cb2fd80e18a886 Added a default value
a03ff84b97f40549ea1e21c4680a2fd3f30eeec4 Using ARGV
19f3881f40ceb5fa20328bf94a749b2c2ba650d7 First Commit
There are a lot of options for selecting which entries are displayed in the log. Play around with the following options:
> git log --pretty=oneline --max-count=2
> git log --pretty=oneline --since='5 minutes ago'
> git log --pretty=oneline --until='5 minutes ago'
> git log --pretty=oneline --author=<your name>
> git log --pretty=oneline --all
See man git-log for all the details.
Here’s what I use to review the changes made in the last week. I’ll add --author=<my-name>
if I only want to see changes I made.
> git log --all --pretty=format:'%h %cd %s (%an)' --since='7 days ago'
Over time, I’ve decided that I like the following log format for most of my work.
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
It looks like this:
> git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
* c51cd70 2022-08-15 | Added a comment (HEAD -> main) [Git Committer]
* 58498b1 2022-08-15 | Added a default value [Git Committer]
* a03ff84 2022-08-15 | Using ARGV [Git Committer]
* 19f3881 2022-08-15 | First Commit [Git Committer]
Let’s look at it in detail:
%h
is the abbreviated hash of the commit%d
are any decorations on that commit (e.g. branch heads or tags)%ad
is the author date%s
is the comment%an
is the author name--graph
informs git to display the commit tree in an ASCII graph layout--date=short
keeps the date format nice and shortThis is a lot to type every time you want to see the log. Fortunately we will learn about git aliases in the next lab.
Both gitx
(for Macs) and gitk
(any platform) are useful in exploring log history.
Next: LAB-12: Aliases
git status
, git add
, git commit
, git switch
and git checkout
are such common commands that it is useful to have abbreviations for them.
Add the following to the .gitconfig file in your $HOME
directory:
\[alias] # remove this comment, and the leading slash (it breaks the layout!)
co = checkout
ci = commit
st = status
br = branch
sw = switch
hist = log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
type = cat-file -t
dump = cat-file -p
We’ve covered the commit and status commands already. And we just covered the log
command in the previous lab. The checkout
command will be coming up soon.
With these aliases defined in the .gitconfig
file you can type git co
wherever you used to have to type git checkout
. Likewise with git st
for git status
and git ci
for git commit
. And best of all, git hist
will allow you to avoid the really long log command.
Go ahead and give the new commands a try.
For the most part, I will continue to type out the full command in these instructions. The only exception is that I will use the hist
alias defined above anytime we need to see the git log
output. Make sure you have a hist
alias setup in your .gitconfig file before continuing if you wish to follow along.
Type
and Dump
We’ve added a few aliases for commands we haven’t covered yet. The git branch
command will be coming up soon. And the git cat-file
command is useful for exploring git, which we will see in a little while.
Going back in history is very easy. The checkout command will copy any snapshot from the repository to the working directory.
git hist
Note: You did remember to define hist
in your .gitconfig
file, right? If not, review the lab on aliases.
> git hist
* db1f4d1 2022-08-16 | Added a comment (HEAD -> main) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
Examine the log output and find the hash for the first commit. It should be the last line of the git hist
output. Use that hash code (the first 7 characters are enough) in the command below. Then check the contents of the hello.rb file.
> git checkout <hash>
> cat hello.rb
You should see something similar to:
> git checkout 19f3881
Note: switching to '19f3881'.
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 switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 19f3881 First Commit
> cat hello.rb
puts "Hello, World"
The output of the checkout
command explains the situation pretty well. Older versions of git will complain about not being on a local branch. In any case, don’t worry about that for now.
Notice the contents of the hello.rb
file are the original contents.
> git switch main
> cat hello.rb
> git checkout main
Previous HEAD position was 19f3881 First Commit
Switched to branch 'main'
> cat hello.rb
# Default is "World"
name = ARGV.first || "World"
puts "Hello, #{name}!"
main
is the name of the default branch. By checking out a branch by name, you go to the latest version of that branch.
Next: LAB-14: Tagging versions
Let’s call the current version of the hello program version 1 (v1).
> git tag v1
Now you can refer to the current version of the program as v1
.
Let’s tag the version immediately prior to the current version v1-beta
. First we need to checkout the previous version. Rather than look up the hash, we will use the `^`` notation to indicate “the parent of v1”.
If the v1^`` notation gives you any trouble, you can also try
v1~1`, which will reference the same version. This notation means “the first ancestor of v1”.
> git checkout v1^
> cat hello.rb
> git checkout v1^
Note: switching to 'v1^'.
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 switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 58498b1 Added a default value
> cat hello.rb
name = ARGV.first || "World"
puts "Hello, #{name}!"
See, this is the version with the default value before we added the comment. Let’s make this v1-beta.
> git tag v1-beta
Now try going back and forth between the two tagged versions.
> git checkout v1
> git checkout v1-beta
> git checkout v1
Previous HEAD position was 58498b1 Added a default value
HEAD is now at c51cd70 Added a comment
> git checkout v1-beta
Previous HEAD position was c51cd70 Added a comment
HEAD is now at 58498b1 Added a default value
You can see what tags are available using the git tag
command.
git tag
> git tag
v1
v1-beta
You can also check for tags in the log.
> git hist main --all
> git hist main --all
* db1f4d1 2022-08-16 | Added a comment (HEAD -> main, tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
You can see both tags (v1
and v1-beta
) listed in the log output, along with the branch name (main
). Also HEAD
shows you the currently checked out commit (which is v1-beta
at the moment).
Let's learn how to roll back a change we've made to the code.
Make sure you are back on the latest commit in main before continuing.
> git checkout main
Sometimes you have modified a file in your local working directory and you wish to just revert to what has already been committed. The checkout command will handle that.
Change hello.rb to have a bad comment.
# This is a bad comment. We want to revert it.
name = ARGV.first || "World"
puts "Hello, #{name}!"
First, check the status of the working directory.
> git status
> git status
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: hello.rb
no changes added to commit (use "git add" and/or "git commit -a")
We see that the hello.rb file has been modified, but hasn’t been staged yet.
Use the restore
command to go back to the repository's version of the hello.rb
file.
> git restore hello.rb
> git status
> cat hello.rb
> git restore hello.rb
> git status
On branch main
nothing to commit, working tree clean
> cat hello.rb
# Default is "World"
name = ARGV.first || "World"
puts "Hello, #{name}!"
The status command shows us that there are no outstanding changes in the working directory. And the “bad comment” is no longer part of the file contents.
Modify the hello.rb
again file to have a bad comment.
# This is an unwanted but staged comment
name = ARGV.first || "World"
puts "Hello, #{name}!"
And then go ahead and stage it.
> git add hello.rb
> git status
git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: hello.rb
The status output shows that the change has been staged and is ready to be committed. Like a lot of git
commands, it also gives us a hint that we can use to go back.
Fortunately the status output tells us exactly what we need to do to unstage the change.
> git restore --staged hello.rb
The restore --staged
command takes the staging area back to be whatever is in HEAD. This clears the staging area of the change we just staged.
Using restore --staged
doesn’t change the working directory. So the working directory still has the unwanted comment in it. We can use the restore
command of the previous lab to remove the unwanted change from the working directory.
> git restore hello.rb
> git status
> git status
On branch main
nothing to commit, working tree clean
And our working directory is clean once again.
Sometimes you realise that a change that you have already committed was not correct and you wish to undo that commit. There are several ways of handling that issue, and the way we are going to use in this lab is always safe.
Essentially we will undo the commit by creating a new commit that reverses the unwanted changes.
Change the hello.rb file to the following.
# This is an unwanted but committed change
name = ARGV.first || "World"
puts "Hello, #{name}!"
Then commit the change.
> git add hello.rb
> git commit -m "Oops, we didn't want this commit"
To undo a committed change, we need to generate a commit that removes the changes introduced by our unwanted commit.
git revert HEAD
This will pop you into the editor. You can edit the default commit message or leave it as is. Save and close the file. You should see the following.
> git revert HEAD
[main 0110f9a] Revert "Oops, we didn't want this commit"
Date: Mon Aug 15 14:54:43 2022 +1000
1 file changed, 1 insertion(+), 1 deletion(-)
Since we were undoing the very last commit we made, we were able to use HEAD
as the argument to revert. We can revert any arbitrary commit earlier in history by simply specifying its hash value.
Checking the log shows both the unwanted and the reverting commits in our repository.
> git hist
> git hist
* 567e59e 2022-08-16 | Revert "Oops, we didn't want this commit" (HEAD -> main) [Brenton Cleeland]
* 148b282 2022-08-16 | Oops, we didn't want this commit [Brenton Cleeland]
* db1f4d1 2022-08-16 | Added a comment (tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
This technique will work with any commit (although you may have to resolve conflicts). It is safe to use even on branches that are publicly shared on remote repositories.
The revert
command of the previous section is a powerful command that lets us undo the effects of any commit in the repository. However, both the original commit and the “undoing” commit are visible in the branch history (using the git log
command).
Often we make a commit and immediately realize that it was a mistake. It would be nice to have a “take back” command that would allow us to pretend that the incorrect commit never happened. The “take back” command would even prevent the bad commit from showing up the git log
history. It would be as if the bad commit never happened.
reset
commandWe’ve already seen the restore
command and have used it to set the staging area to be consistent with a given commit (we used the HEAD commit in our previous lab). The reset
command is a similar command that lets us completely remove commits from a branch.
When given a commit reference (i.e. a hash, branch or tag name), the reset
command will:
Let's do a quick check of our commit history.
> git hist
> git hist
* 567e59e 2022-08-16 | Revert "Oops, we didn't want this commit" (HEAD -> main) [Brenton Cleeland]
* 148b282 2022-08-16 | Oops, we didn't want this commit [Brenton Cleeland]
* db1f4d1 2022-08-16 | Added a comment (tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
We see that we have an “Oops” commit and a “Revert Oops” commit as the last two commits made in this branch. Let’s remove them using reset.
But before we remove the commits, let’s mark the latest commit with a tag so we can find it again.
> git tag oops
Looking at the log history (above), we see that the commit tagged v1
is the commit right before the bad commit. Let’s reset the branch to that point. Since that branch is tagged, we can use the tag name in the reset command (if it wasn’t tagged, we could just use the hash value).
> git reset --hard v1
> git hist
git reset --hard v1
HEAD is now at db1f4d1 Added a comment
* db1f4d1 2022-08-16 | Added a comment (HEAD -> main, tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
Our main branch now points to the v1 commit and the Oops commit and the Revert Oops commit are no longer in the branch. The --hard
parameter indicates that the working directory should be updated to be consistent with the new branch head.
But what happened to the bad commits? It turns out that the commits are still in the repository. In fact, we can still reference them. Remember that at the beginning of this lab we tagged the reverting commit with the tag “oops”. Let’s look at all the commits.
git hist --all
git hist --all
* 567e59e 2022-08-16 | Revert "Oops, we didn't want this commit" (tag: oops) [Brenton Cleeland]
* 148b282 2022-08-16 | Oops, we didn't want this commit [Brenton Cleeland]
* db1f4d1 2022-08-16 | Added a comment (HEAD -> main, tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
Here we see that the bad commits haven’t disappeared. They are still in the repository. It’s just that they are no longer listed in the main branch. If we hadn’t tagged them, they would still be in the repository, but there would be no way to reference them other than using their hash names. Commits that are unreferenced remain in the repository until the system runs the garbage collection software.
Resets on local branches are generally safe. Any “accidents” can usually be recovered from by just resetting again with the desired commit.
However, if the branch is shared on remote repositories, resetting can confuse other users sharing the branch. As a general rule you should revert
not reset
if you have shared your branch by pushing it to a remote repository.
The oops tag has served its purpose. Let’s remove it and allow the commits it referenced to be garbage collected.
> git tag -d oops
> git hist --all
> git tag -d oops
Deleted tag 'oops' (was 567e59e)
> git hist --all
* db1f4d1 2022-08-16 | Added a comment (HEAD -> main, tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
The oops tag is no longer listed in the repository.
Let's add a simple author comment to the program.
# Default is World
# Author: <your name>
name = ARGV.first || "World"
puts "Hello, #{name}!"
> git add hello.rb
> git commit -m "Add an author comment"
After you make the commit, you realize that any good author comment should have an email included. Update the hello program to include an email.
# Default is World
# Author: <your name> (me@example.org)
name = ARGV.first || "World"
puts "Hello, #{name}!"
We really don’t want a separate commit for just the email. Let’s amend the previous commit to include the email change.
> git add hello.rb
> git commit --amend -m "Add an author/email comment"
> git add hello.rb
> git commit --amend -m "Add an author/email comment"
[main af90521] Add an author/email comment
Date: Mon Aug 15 14:54:43 2022 +1000
1 file changed, 2 insertions(+), 1 deletion(-)
> git hist
> git hist
* af90521 2022-08-16 | Add an author/email comment (HEAD -> main) [Brenton Cleeland]
* db1f4d1 2022-08-16 | Added a comment (tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
We can see the original “author” commit is now gone, and it is replaced by the “author/email” commit. You can achieve the same effect by resetting the branch back one commit and then recommitting the new changes.
Similarly to the reset
command in the previous lab, you need to be careful not to ammend commits that you've already shared with others by pushing to a remote repository.
Next: LAB-20: Moving Files
We are now going to build up the structure of our little repository. Let’s move the program into a lib directory.
> mkdir lib
> git mv hello.rb lib
> git status
> mkdir lib
> git mv hello.rb lib
> git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
renamed: hello.rb -> lib/hello.rb
By using git to do the move, we inform git of 2 things
Both of these bits of information are immediately staged and ready to be committed. The git status
command reports that the file has been moved.
One of the nice things about git is that you can forget about source control until the point you are ready to start committing code. What would happen if we used the operating system command to move the file instead of the git command?
It turns out the following set of commands is identical to what we just did. It’s a bit more work, but the result is the same.
We could have done:
> mkdir lib
> mv hello.rb lib
> git add lib/hello.rb
> git rm hello.rb
Let's commit this change before we move on.
> git commit -m "Moved hello.rb to lib"
Let's take a quick detour and explore some of git's internals. First, from the root of your project directory:
> ls -C .git
> ls -C .git
COMMIT_EDITMSG config index objects
HEAD description info packed-refs
ORIG_HEAD hooks logs refs
This is the magic directory where all the git “stuff” is stored. Let’s peek in the objects directory.
> ls -C .git/objects
> ls -C .git/objects
01 11 27 43 69 7d 98 af c5 info
05 19 28 58 6b 94 9c b5 e4 pack
09 24 37 59 78 97 a0 c4 e7
You should see a bunch of directories with 2 letter names. The directory names are the first two letters of the sha1 hash of the object stored in git.
> ls -C .git/objects/<dir>
> ls -C .git/objects/01
10f9a5cf0047ac7cb1938734e238f48a47ffb4
Look in one of the two-letter directories. You should see some files with 38-character names. These are the files that contain the objects stored in git. These files are compressed and encoded, so looking at their contents directly won’t be very helpful, but we will take a closer look in a bit.
> cat .git/config
> cat .git/config
\[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
\[user]
name = Git User
email = me@example.org
This is a project-specific configuration file. Config entries in here will override the config entries in the .gitconfig file in your home directory, at least for this project.
> ls .git/refs
> ls .git/refs/heads
> ls .git/refs/tags
> cat .git/refs/tags/v1
```output
> ls .git/refs
heads
tags
> ls .git/refs/heads
main
> ls .git/refs/tags
v1
v1-beta
> cat .git/refs/tags/v1
c51cd709b284ba82ae2c7d1578e242cd11dc8a0e
You should recognize the files in the tags subdirectory. Each file corresponds to a tag you created with the git tag command earlier. Its content is just the hash of the commit tied to the tag.
The heads directory is similar, but is used for branches rather than tags. We only have one branch at the moment, so all you will see is main in this directory.
> cat .git/HEAD
```output
> cat .git/HEAD
ref: refs/heads/main
The HEAD file contains a reference to the current branch. It should be a reference to main at this point.
It’s time to do a major rewrite of the hello world functionality. Since this might take awhile, you’ll want to put these changes into a separate branch to isolate them from changes in main.
Let’s call our new branch greet
.
> git checkout -b greet
> git status
NOTE: git checkout -b <branchname>
is a shortcut for git branch <branchname>
followed by a git switch <branchname>
.
Notice that the git status command reports that you are on the greet
branch.
Create a new file called lib/greeter.rb
with the following content:
class Greeter
def initialize(who)
@who = who
end
def greet
"Hello, #{@who}"
end
end
Then add and commit the new file:
> git add lib/greeter.rb
> git commit -m "Added greeter class"
Update hello.rb
to use the greeter class:
require_relative 'greeter'
# Default is World
name = ARGV.first || "World"
greeter = Greeter.new(name)
puts greeter.greet
Add and commit this change as well:
> git add lib/hello.rb
> git commit -m "Hello uses Greeter"
We now have a new branch called greet with 2 new commits on it. Next we will learn how to navigate and switch between branches.
> git switch main
Create a new file called README.md
with the following content:
This is the Hello World example from the git tutorial.
> git add README.md
> git commit -m "Added README.md"
We now have two diverging branches in the repository. Use the hist
alias we created earlier to view the branches and how they diverge.
> git hist --all
* 22d8379 2022-08-16 | Added README.md (HEAD -> main) [Brenton Cleeland]
| * 9ca04ea 2022-08-16 | Hello uses Greeter (greet) [Brenton Cleeland]
| * 34c8660 2022-08-16 | Added greeter class [Brenton Cleeland]
|/
* 41ff07c 2022-08-16 | Moved hello.rb to lib [Brenton Cleeland]
* af90521 2022-08-16 | Add an author/email comment [Brenton Cleeland]
* db1f4d1 2022-08-16 | Added a comment (tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
Here is our first chance to see the --graph
option on git hist
in action. Adding the --graph
option to git log
causes it to draw the commit tree using simple ASCII characters. We can see both branches (greet and main), and that the main branch is the current HEAD.
The --all
flag makes sure that we see all the branches. The default is to show only the current branch.
Next: LAB-26: Merging
Merging brings the changes in two branches together. Let’s go back to the greet branch and merge main onto greet.
> git switch greet
> git merge main
> git hist --all
> git merge main
Merge made by the 'ort' strategy.
README.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 README.md
> git hist --all
* 4f17759 2022-08-16 | Merge branch 'main' into greet (HEAD -> greet) [Brenton Cleeland]
|\
| * 22d8379 2022-08-16 | Added README.md (main) [Brenton Cleeland]
* | 9ca04ea 2022-08-16 | Hello uses Greeter [Brenton Cleeland]
* | 34c8660 2022-08-16 | Added greeter class [Brenton Cleeland]
|/
* 41ff07c 2022-08-16 | Moved hello.rb to lib [Brenton Cleeland]
* af90521 2022-08-16 | Add an author/email comment [Brenton Cleeland]
* db1f4d1 2022-08-16 | Added a comment (tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
By merging main into your greet branch periodically, you can pick up any changes to main and keep your changes in greet compatible with changes in the mainline.
However, it does produce ugly commit graphs. Later we will look at the option of rebasing rather than merging.
But first, what if the changes in main conflict with the changes in greet?
Switch back to the main branch and make this change:
> git switch main
Update lib/hello.rb
with the following:
puts "What's your name"
my_name = gets.strip
puts "Hello, #{my_name}!"
> git add lib/hello.rb
> git commit -m "Made interactive"
We now have changes to lib/hello.rb
on both of our active branches. A common situation if you have more than one person working on your codebase!
> git hist --all
> git hist --all
* ef40b54 2022-08-16 | Made interactive (HEAD -> main) [Brenton Cleeland]
| * 4f17759 2022-08-16 | Merge branch 'main' into greet (greet) [Brenton Cleeland]
| |\
| |/
|/|
* | 22d8379 2022-08-16 | Added README.md [Brenton Cleeland]
| * 9ca04ea 2022-08-16 | Hello uses Greeter [Brenton Cleeland]
| * 34c8660 2022-08-16 | Added greeter class [Brenton Cleeland]
|/
* 41ff07c 2022-08-16 | Moved hello.rb to lib [Brenton Cleeland]
* af90521 2022-08-16 | Add an author/email comment [Brenton Cleeland]
* db1f4d1 2022-08-16 | Added a comment (tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
Main at commit “Added README” has been merged to the greet branch, but there is now an additional commit on main that has not been merged back to greet.
Now go back to the greet branch and try to merge the new main.
> git checkout greet
> git merge main
> git merge main
Auto-merging lib/hello.rb
CONFLICT (content): Merge conflict in lib/hello.rb
Automatic merge failed; fix conflicts and then commit the result.
If you open lib/hello.rb
you will see git's conflict markers in the file:
<<<<<<< HEAD
require_relative 'greeter'
# Default is World
name = ARGV.first || "World"
greeter = Greeter.new(name)
puts greeter.greet
=======
puts "What's your name"
my_name = gets.strip
puts "Hello, #{my_name}!"
>>>>>>> main
The first section is the version on the head of the current branch (greet). The second section is the version on the main branch.
You need to manually resolve the conflict. In this case we want to keep a version that supports both pieces of functionality. Modify lib/hello.rb
to be the following:
require_relative 'greeter'
puts "What's your name"
my_name = gets.strip
greeter = Greeter.new(my_name)
puts greeter.greet
> git add lib/hello.rb
> git commit -m "Merged main, fixed conflict."
> git add lib/hello.rb
> git commit -m "Merged main, fixed conflict."
[greet 8822978] Merged main, fixed conflict.
Git doesn't provide any graphical merge tools, but it will gladly work with any third party merge tool you with to use. Most modern editors (like VS Code) include merge tools that you can test. The Customising Git Configuration section of the Git Book includes details about configuring Perforce as your default merge tool.
Let’s explore the differences between merging and rebasing. In order to do so, we will make a new change on the main
branch, then use git rebase
to move that onto our greet branch.
Start by switching to main
:
> git switch main
Add a thank you to the original tutorial author in the README.md:
This is the Hello World example from the git tutorial.
Thanks to Jim Weirich for the initial work on this tutorial for RailsConf 2010.
Now, lets commit that change on main.
> git add README.md
> git commit -m "Add a thank you to Jim"
While still on the main
branch, check the history.
> git hist --all
git hist --all
* f6a7d74 2022-08-16 | Add a thank you to Jim (HEAD -> main) [Brenton Cleeland]
| * 8822978 2022-08-16 | Merged main, fixed conflict. (greet) [Brenton Cleeland]
| |\
| |/
|/|
* | ef40b54 2022-08-16 | Made interactive [Brenton Cleeland]
| * 4f17759 2022-08-16 | Merge branch 'main' into greet [Brenton Cleeland]
| |\
| |/
|/|
* | 22d8379 2022-08-16 | Added README.md [Brenton Cleeland]
| * 9ca04ea 2022-08-16 | Hello uses Greeter [Brenton Cleeland]
| * 34c8660 2022-08-16 | Added greeter class [Brenton Cleeland]
|/
* 41ff07c 2022-08-16 | Moved hello.rb to lib [Brenton Cleeland]
* af90521 2022-08-16 | Add an author/email comment [Brenton Cleeland]
* db1f4d1 2022-08-16 | Added a comment (tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
In the log you can see the new commit at the top. Switch to the greet
branch and view the history again, it should look the same.
> git switch greet
> git hist --all
> git hist --all
* f6a7d74 2022-08-16 | Add a thank you to Jim (main) [Brenton Cleeland]
| * 8822978 2022-08-16 | Merged main, fixed conflict. (HEAD -> greet) [Brenton Cleeland]
| |\
| |/
|/|
* | ef40b54 2022-08-16 | Made interactive [Brenton Cleeland]
| * 4f17759 2022-08-16 | Merge branch 'main' into greet [Brenton Cleeland]
| |\
| |/
|/|
* | 22d8379 2022-08-16 | Added README.md [Brenton Cleeland]
| * 9ca04ea 2022-08-16 | Hello uses Greeter [Brenton Cleeland]
| * 34c8660 2022-08-16 | Added greeter class [Brenton Cleeland]
|/
* 41ff07c 2022-08-16 | Moved hello.rb to lib [Brenton Cleeland]
* af90521 2022-08-16 | Add an author/email comment [Brenton Cleeland]
* db1f4d1 2022-08-16 | Added a comment (tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
This time we will use the rebase command instead of the merge command to bring in the changes from the main branch.
> git rebase main
> git rebase main
Auto-merging lib/hello.rb
CONFLICT (content): Merge conflict in lib/hello.rb
error: could not apply 9ca04ea... Hello uses Greeter
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 9ca04ea... Hello uses Greeter
Ut, oh! We have the same conflict during this rebase that we resolved earlier. This is a great example of why it can be tricky to mix rebasing and merging together.
Resolve the conflict by updating lib/hello.rb
with the expected content:
require_relative 'greeter'
puts "What's your name"
my_name = gets.strip
greeter = Greeter.new(my_name)
puts greeter.greet
Then continue through the rebase:
> git add lib/hello.rb
> git rebase --continue
> git add lib/hello.rb
> git rebase --continue
[detached HEAD 630033d] Hello uses Greeter
1 file changed, 4 insertions(+), 1 deletion(-)
Successfully rebased and updated refs/heads/greet.
Finally, run git hist
again to see what's happened to our "Add a thank you" commit.
> git hist
> git hist
* 630033d 2022-08-16 | Hello uses Greeter (HEAD -> greet) [Brenton Cleeland]
* 55d0c6b 2022-08-16 | Added greeter class [Brenton Cleeland]
* f6a7d74 2022-08-16 | Add a thank you to Jim (main) [Brenton Cleeland]
* ef40b54 2022-08-16 | Made interactive [Brenton Cleeland]
* 22d8379 2022-08-16 | Added README.md [Brenton Cleeland]
* 41ff07c 2022-08-16 | Moved hello.rb to lib [Brenton Cleeland]
* af90521 2022-08-16 | Add an author/email comment [Brenton Cleeland]
* db1f4d1 2022-08-16 | Added a comment (tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
Notice that our "Add a thank you" commit has moved before the new commits on our greet
branch. This has made our commit history completely linear.
The final result of the rebase is very similar to the merge.
The greet branch now contains all of its changes, as well as all the changes from the main branch. However, the commit tree is quite different.
The commit tree for the greet branch has been rewritten so that the main branch is a part of the commit history. This leaves the chain of commits linear and much easier to read.
Don’t use rebase...
Given the above guidelines, I tend to use rebase for short-lived, local branches and merge for branches in the public repository.
We’ve kept our greet branch up to date with main (via rebase), now let’s merge the greet changes back into the main branch.
> git checkout main
> git merge greet
> git checkout main
Switched to branch 'main'
> git merge greet
Updating f6a7d74..630033d
Fast-forward
lib/greeter.rb | 8 ++++++++
lib/hello.rb | 5 ++++-
2 files changed, 12 insertions(+), 1 deletion(-)
create mode 100644 lib/greeter.rb
Because the head of main is a direct ancestor of the head of the greet branch, git is able to do a fast-forward merge. When fast-forwarding, the branch pointer is simply moved forward to point to the same commit as the greeter branch.
There will never be conflicts in a fast-forward merge.
git hist
> git hist
* 630033d 2022-08-16 | Hello uses Greeter (HEAD -> main, greet) [Brenton Cleeland]
* 55d0c6b 2022-08-16 | Added greeter class [Brenton Cleeland]
* f6a7d74 2022-08-16 | Add a thank you to Jim [Brenton Cleeland]
* ef40b54 2022-08-16 | Made interactive [Brenton Cleeland]
* 22d8379 2022-08-16 | Added README.md [Brenton Cleeland]
* 41ff07c 2022-08-16 | Moved hello.rb to lib [Brenton Cleeland]
* af90521 2022-08-16 | Add an author/email comment [Brenton Cleeland]
* db1f4d1 2022-08-16 | Added a comment (tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
The greet and main branches are now identical.
Up to this point we have been working with a single git repository. However, git excels at working with multiple repositories. These extra repositories may be stored locally, or may be accessed across a network connection. In modern workflows you will use a hosted remote repository (i.e. on Github or Azure DevOps) as a way of sharing your code with other developers on your team.
In the next section we will create a new repository called “cloned_hello”. We will show how to move changes from one repository to another, and how to handle conflicts when they arise between two repositories.
For now, we will be working with local repositories (i.e. repositories stored on your local hard disk), however most of the things learned in this section will apply to multiple repositories whether they are stored locally or remotely over a network.
NOTE: We are going be making changes to both copies of our repositories. Make sure you pay attention to which repository you are in at each step of the following labs.
First, we need to move up one directory in our terminal.
> cd ..
> ls
In the output of ls
you should see your hello
directory.
> ls
hello
Let's make a clone of the repository.
> git clone hello cloned_hello
> ls
> git clone hello cloned_hello
Cloning into 'cloned_hello'...
done.
> ls
cloned_hello hello
There should now be two repositories in your working directory: the original “hello” repository and the newly cloned “cloned_hello” repository.
Let’s take a look at the cloned repository.
> cd cloned_hello
> ls
> ls
README.md lib
You should see a list of all the files in the top level of the original repository (README and lib). You've created a complete copy of the "hello" repository.
> git hist --all
* 630033d 2022-08-16 | Hello uses Greeter (HEAD -> main, origin/main, origin/greet, origin/HEAD) [Brenton Cleeland]
* 55d0c6b 2022-08-16 | Added greeter class [Brenton Cleeland]
* f6a7d74 2022-08-16 | Add a thank you to Jim [Brenton Cleeland]
* ef40b54 2022-08-16 | Made interactive [Brenton Cleeland]
* 22d8379 2022-08-16 | Added README.md [Brenton Cleeland]
* 41ff07c 2022-08-16 | Moved hello.rb to lib [Brenton Cleeland]
* af90521 2022-08-16 | Add an author/email comment [Brenton Cleeland]
* db1f4d1 2022-08-16 | Added a comment (tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
You should now see a list of all the commits in the new repository, and it should (more or less) match the history of commits in the original repository. The only difference should be in the names of the branches.
You can see the main branch (along with HEAD) in the history list. But you will also have a number of strangely named branches (origin/main, origin/greet and origin/HEAD). We’ll talk about those in a bit.
Next: LAB-34: What is Origin
Let's learn a little about naming remote repositories.
> git remote
> git remote
origin
We see that the cloned repository knows about a remote repository named origin. Let’s see if we can get more information about origin:
> git remote show origin
* remote origin
Fetch URL: /Users/brecleel/Dev/hello
Push URL: /Users/brecleel/Dev/hello
HEAD branch: main
Remote branches:
greet tracked
main tracked
Local branch configured for 'git pull':
main merges with remote main
Local ref configured for 'git push':
main pushes to main (up to date)
Now we see that the remote repository “origin” is simply the original hello repository on our filesystem.
Remote repositories typically live on a separate machine, possibly a centralized server. As we can see here, however, they can just as well point to a repository on the same machine.
There is nothing particularly special about the name “origin”, however the convention is to use the name “origin” for the primary centralized repository (if there is one).
Next: LAB-35: Remote Branches
Let's look at the branches available in our cloned repository.
> git branch
> git branch
* main
That's it! Only the main branch is listed. Where is the greet branch? The git branch
command only lists the local branches by default.
To see all branches you need to use the -a
flag on git branch
.
> git branch -a
* main
remotes/origin/HEAD -> origin/main
remotes/origin/greet
remotes/origin/main
Git has all the commits from the original repository, but branches in the remote repository are not treated as local branches here. If we want our own greet branch, we need to create it ourselves. We will see how to do that in a minute.
First, move back into the origin hello
repository.
> cd ../hello
Now, make a change to the README.md file:
This is the Hello World example from the git tutorial.
Thanks to Jim Weirich for the initial work on this tutorial for RailsConf 2010.
(changed in original)
Add and commit the change.
> git add README.md
> git commit -m "Changed README in the original repo"
The original repository now has later changes that are not in the cloned version. Next we will pull those changes across to the cloned repository.
Next: LAB-37: Fetching Changes
Move into the cloned_hello
repository and check the history.
> cd ../cloned_hello
> git hist --all
> cd ../cloned_hello/
> git hist --all
* 630033d 2022-08-16 | Hello uses Greeter (HEAD -> main, origin/main, origin/greet, origin/HEAD) [Brenton Cleeland]
* 55d0c6b 2022-08-16 | Added greeter class [Brenton Cleeland]
* f6a7d74 2022-08-16 | Add a thank you to Jim [Brenton Cleeland]
* ef40b54 2022-08-16 | Made interactive [Brenton Cleeland]
* 22d8379 2022-08-16 | Added README.md [Brenton Cleeland]
* 41ff07c 2022-08-16 | Moved hello.rb to lib [Brenton Cleeland]
* af90521 2022-08-16 | Add an author/email comment [Brenton Cleeland]
* db1f4d1 2022-08-16 | Added a comment (tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
Notice that the new "Changed README in origin repo" commit isn't yet available in this repository.
The git fetch
command is used to retrieve the latest version of the repository from our remote.
> git fetch
> git hist --all
> git fetch
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 333 bytes | 111.00 KiB/s, done.
From /Users/brecleel/Dev/hello
630033d..35263dc main -> origin/main
> git hist --all
* 35263dc 2022-08-17 | Changed README in the original repo (origin/main, origin/HEAD) [Brenton Cleeland]
* 630033d 2022-08-16 | Hello uses Greeter (HEAD -> main, origin/greet) [Brenton Cleeland]
* 55d0c6b 2022-08-16 | Added greeter class [Brenton Cleeland]
* f6a7d74 2022-08-16 | Add a thank you to Jim [Brenton Cleeland]
* ef40b54 2022-08-16 | Made interactive [Brenton Cleeland]
* 22d8379 2022-08-16 | Added README.md [Brenton Cleeland]
* 41ff07c 2022-08-16 | Moved hello.rb to lib [Brenton Cleeland]
* af90521 2022-08-16 | Add an author/email comment [Brenton Cleeland]
* db1f4d1 2022-08-16 | Added a comment (tag: v1) [Brenton Cleeland]
* cebd1e7 2022-08-16 | Added a default value (tag: v1-beta) [Brenton Cleeland]
* 3f2c477 2022-08-16 | Using ARGV [Brenton Cleeland]
* 5dced27 2022-08-16 | First Commit [Brenton Cleeland]
At this point the repository has all the commits from the original repository, but they are not integrated into the cloned repository’s local branches.
Find the “Changed README in original repo” commit in the history above. Notice that the commit includes “origin/main” and “origin/HEAD”.
Now look at the “Hello uses Greeter” commit. You will see that the local main branch points to this commit, not to the new commit that we just fetched.
The upshot of this is that the git fetch
command will fetch new commits from the remote repository, but it will not merge these commits into the local branches.
We can demonstrate that the cloned README is unchanged.
> cat README.md
This is the Hello World example from the git tutorial.
Thanks to Jim Weirich for the initial work on this tutorial for RailsConf 2010.
See, this is the original README.md contents from before we made our change in the hello
repository.
The local and origin
main branches are different, and you can use git merge
to bring them into line.
> git merge origin/main
> git merge origin/main
Updating 630033d..35263dc
Fast-forward
README.md | 2 ++
1 file changed, 2 insertions(+)
We should see the changes now.
> cat README.md
> cat README.md
This is the Hello World example from the git tutorial.
Thanks to Jim Weirich for the initial work on this tutorial for RailsConf 2010.
(changed in original)
We’re not going to go through the process of creating another change and merging it again, but we do want you to know that doing:
> git pull
Is the equivalent of doing the two steps we just did in a single command.
> git fetch
> git merge origin/main
In order to push a change to a remote, the remote needs to be a "bare" repository. Bare repositories don't have working directories, and a effectively just the content of the .git
directory. When you create a repository with a service like Github the repository that they host will be considered bare (though it might not look like it in the user interface).
Let's create a bare repository from our hello
repository.
> cd ..
> git clone --bare hello hello.git
> ls hello.git
> git clone --bare hello hello.git
Cloning into bare repository 'hello.git'...
done.
> ls hello.git
HEAD config description hooks info objects packed-refs refs
The convention is that repositories ending in ‘.git’ are bare repositories. We can see that there is no working directory in the hello.git repo. Essentially it is nothing but the .git directory of a non-bare repo.
cloned_hello
Move back into the cloned_hello repository and add our new bare repository as a remote.
cd cloned_hello
git remote add shared ../hello.git
We've named the remote "shared" in the command above.
Next: LAB-40: Push a change
cloned_hello
repoLet's push a change to our new bare remote.
Update the README.md in your cloned_hello
repository:
This is the Hello World example from the git tutorial.
Thanks to Jim Weirich for the initial work on this tutorial for RailsConf 2010.
(changed in original, then changed in cloned_hello)
Add and commit the change using the commands you've learned today.
> git add README.md
> git commit -m "Update README from our cloned repo"
> git commit -m "Update README from our cloned repo"
[main 1dc8db6] Update README from our cloned repo
1 file changed, 1 insertion(+), 1 deletion(-)
git push
to publish our changeIn the command below "shared" is the name of our remote, and "main" is the name of the branch we are pushing. It's possible to set up branches to "track" remote branches automatically (allowing you to simply git push
) but being explicit about where and what your are pushing is good practice.
> git push shared main
git push shared main
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 364 bytes | 364.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
To ../hello.git
35263dc..1dc8db6 main -> main
Your change has now been pushed to the remote and is shared with others.
Sign in to the account you created earlier and create a new repository on Github. Choose a name for your repo (I chose "hello" but you can choose anything you like).
From the empty repository screen, copy the SSH URL for the repository. It should look similar to git@github.com:brntn-ps/hello.git
but with your username instead of mine.
Add the repository as a remote for our hello repo.
> git remote add github git@github.com:brntn-ps/hello.git
Using the same git push
command as we did before, we'll push our code to the new github
remote.
> git push github main
git push github main
Enumerating objects: 41, done.
Counting objects: 100% (41/41), done.
Delta compression using up to 8 threads
Compressing objects: 100% (32/32), done.
Writing objects: 100% (41/41), 3.91 KiB | 572.00 KiB/s, done.
Total 41 (delta 4), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (4/4), done.
To github.com:brntn-ps/hello.git
* [new branch] main -> main
If you navigate to the repository in your browser and refresh, you should now see our code.
Next: Next steps with Git
A guided tour that walks through the fundamentals of Git, inspired by the premise that to know a thing is to do it.
git
is an industry standard version control system. Today's workshop will give you a deep dive into how to use it on a project.
Large parts of this tutorial are based on the Git Immersion tutorial which was originally presented at RailsConf 2010 by Jim Weirich. That tutorial is licensed as CC-BY-SA and this updated tutorial retains that license.
Changes from the original:
master
to main
as the default branchrestore
instead of checkout
to roll back unstaged changesrestore --staged
instead of reset
to roll back staged changesruby
availableswitch
instead of checkout
to change branchesThis version of Git Immersion was updated in 2022 by Brenton Cleeland for Publicis Sapient Australia for our Early Careers program. Like the original, it is released under a Creative Commons Attribution Non-Commercial Share-Alike 4.0 International license.