CSE 373, Spring 2019: Intro to Git, Part 2

Preface

We mentioned before that Git is very flexible; this has a couple important implications:

  1. We won't be able to cover everything you can do with Git. Git has many features, and they can be combined in many different workflows; for simplicity, we'll be describing only the bare minimum set of features necessary to get started with Git. You may use other Git functionality, but we will not be providing guides for those. You can check out the official Git documentation for more information or search for other resources. (GitHub, GitLab, and Atlassian tend to have some pretty nice stuff, since they all have repo hosting services.)

  2. You should be careful when using Git. Git is a powerful tool, and some of the more powerful functionality may destroy your local repository if used incorrectly. None of the functionality we go over should allow you to do this, but you should definitely understand what you're doing before running random Git commands you find on the internet.

Fortunately, GitLab should prevent you from destroying the remote repo, so in the worst case, you can just download the code from the remote repo again. You can also come in to office hours for help with Git. (The discussion board tends not to work too well for debugging issues with Git though.)

How does Git work?

You may have noticed that after you clone a repository using Git, the resulting directory on your machine contains the current version of the repo's files, but with an extra .git directory inside. (If you're using Mac or Linux, this folder may be directory by default.) This .git directory contains all the extra data Git stores, such as the history of changes—in fact, this directory is the actual local repository, a local version of what's stored on GitLab. Meanwhile, we refer to the directory containing this directory and the other, regular files as the working directory. (However, typically, whenever we refer verbally to the directory or path of a Git repository on your machine, we mean the working directory and not the .git directory inside; this is a minor inconsistency in terminology, so do ask if it's not clear which we're referring to.)

This means that there are really two versions of code on your machine: the working directory will contain the copy of the code that you're actively working on as real files, whereas the local repository will store the entire history of the project as a series of changes between sequential versions.

Regular Git usage involves syncing changes between the working directory and the local repository, and between the local repository and the remote repository. Syncing changes in Git always works in a single direction at a time: new changes get copied from one place to another. Here's a diagram with the names for basic commands that move changes in each direction:

As you can see, there are three main commands:

  • Committing adds changes from the working directory to the local repo.
  • Pushing copies changes from the local repo to the remote repo.
  • Pulling is a little more complicated: it first copies changes from the remote repo to the local repo, then applies those changes to the working directory.

Committing changes

Committing is the main way that changes enter Git repos (and the only way in this basic workflow) since pushing and pulling involve moving changes between repos or from the local repo out and into the working directory. Whenever you commit, Git compares the working directory to the local repo and adds the set of changes to the local repo. This set of changes is referred to as a commit; at the same time, since a commit also stores a reference to the previous/parent commit (kind of like a linked list), we can trace the commits all the way back to the beginning of the commit history; in essence, this means that a commit also specifies a particular version of code.

Now, referring to commits by their full list of changes is pretty awkward, and referring to them by the contents of all files is even worse, so Git lets us assign a commit message when we're committing to briefly describe the changes made. Also, internally, Git generates a commit hash for each commit—a string of hexadecimal characters that acts as an ID for the commit. The commit hash is usually not very useful for humans, but in this course, we include it in our project feedback to indicate which commit we graded.

There's actually a little bit more to committing than this: Git allows us to choose exactly which changes in the working directory to commit, which involves keeping another version of code between the working directory and the local repo; however, we'll be using a GUI that manages that for us, so we'll ignore it.

Before we begin, note that IntelliJ marks local changes right in the editor:

The colors to the right of the line numbers and @ symbol denote changes in your working directory (compared to your local repository). The green highlight marks an added line, the gray right-pointing triangle marks removed lines, and the blue highlight marks a changed line.

These markers are very useful for checking that you haven't accidentally modified code that you were not supposed to modify. You can also list all changed files by opening the "Version Control" tool window and selecting the "Local Changes" tab:

(If you're using the single-project workflow, you may want to use the "Group By:" selector in the left-side toolbar of that tool window to group changes by module.)

It may be useful to show the diff pane in this tool window, which will show a side-by-side comparison of the file before and after your local changes. (The button for this is in the left-side toolbar of the tool window, at the very bottom—it's actually hidden in the overflow menu in the screenshot below.)

To commit changes:

  1. Open the "Version Control" tool window and select the "Local Changes" tab. Then, click the green check mark button on the left-side toolbar in that tool window.

  2. A "Commit Changes" window should open. In this window, select the files you wish to commit and enter a commit message to describe your changes. This window also includes a diff viewer that allows you to view local changes.

  3. At this point, you may want to review the "Before Commit" options in this window. These options control what automatic checks and changes IntelliJ will make before committing your code. The relevant options for our class are enabled in the image below.

    You aren't required to enable any of these, but...

    • We strongly recommend enabling at least the Checkstyle scan.
    • The "Reformat code" can also be useful for ensuring that your code conforms to our style, but could also ensure that your code doesn't conform to our style if your IDE is configured incorrectly.
    • The "Perform code analysis" option will also run Checkstyle, but it will group those errors with any others in the project, so it's easy to accidentally ignore them. (Also, this option is just annoying in general since it will pop up a dialogue box if there are any warnings in the code.
    • The "Check TODO (Show All)" option may help make sure there aren't any TODO's left in code.
  4. When you're done, click the "Commit" button in the bottom right to finish committing. If you want to commit and push at the same time (which is reasonable for small projects like ours, but is less useful in large projects), you can also use the dropdown arrow and choose "Commit and Push" instead.

Pushing changes

Pushing is relatively simple, since it just involves copying commits from the local repo to the remote repo.

  1. Click "VCS" > "Git" > "Push..."

  2. The window that opens will show a preview of all local commits that you will push to, the remote repository. In this window, click "Push".

Pulling changes

Pulling is fairly simple for the same reason that pushing is: usually, it just copies new commits from the remote repo to the local repo, then applies the changes from those commits to the working directory.

  1. Click "VCS" > "Git" > "Pull..."

  2. In the window that opens, click "Pull".

    If you're using the single-project workflow and you already have multiple modules in your project, you may need to first select the proper "Git Root" (i.e., the directory containing the assignment repository).

Other features

Again, there are many other Git features not covered here for the sake of brevity. You can see the IntelliJ documentation for more details.

One particularly nice feature is the Log tab in the Version Control tool window, which displays a visual representation of the commit history in your Git repositories: see this page for an overview or this page for the full documentation

There's also a bit more to learn about Git once we start working on partner projects, but we'll leave that information for later.

Note: be careful when you're playing around with IntelliJ's Git features, since Git allows you to completely mess up your local repository, and if you accidentally you push your changes to GitLab, the remote repository as well. In general, if you're not sure what something does and it doesn't seem to be a visual/display option, you should consult IntelliJ/Git documentation before trying it.)