8.3 Making commits and pushing to the remote
Everything we’ve done to this point may seem like a fair amount of work just to get setup, but this process quickly becomes second nature. And, now that we are setup to monitor all of our files, we can make some changes, commit those changes locally, and then push the changes to our remote repository.
Important git vocabulary
Stage: Prepare a file (or files) for a commit. All files in the staging area during a specific commit will be associated with the same commit and commit message.
Commit: This is where you will, essentially, save the version of the file(s) in the repo. You commit the changes to the repo.
Push: Upload changes from your local repository to the remote repository. Note this will only push the most recent changes.
Pull: Download the most recent changes from the remote repository to your local repository. Note this will only pull the most recent changes.
Let’s go back to what our repository looked like after initializing locally (starting from an existing project).
Notice that we have four files listed in the “Unstaged Files” pane, including one called “.DS_Store”, which is an internal file used on Mac machines. This is an example of a file that we should ignore. It’s irrelevant to our project and, beyond cluttering our repo, could actually make things more difficult for us in collaborating with others. Let’s go into RStudio, open our .gitignore file, and add
Then if we go back to GitKraken, you will see that immediately the .DS_Store file is gone and, similarly, we see the .gitignore file shows up in its place, but with a different icon. The green plus symbol shown by the other files imply they are new, while the pencil indicates the file has been modified since the previous commit. If we click on the file (in the Unstaged Files pane) it will open up a diff view, showing us the difference between the file as it currently exists relative to its previous state (i.e., since the last time the file was comitted to the repository). In this case, we see two new green lines, indicating two lines were added (lines that have been removed show up in red) with one of them including the text “.DS_Store”.
To commit files, we have to first stage them. As shown in the box above, files that are staged together will be associated with the same commit and the same commit message. Let’s first stage the new files and add a commit message. Ideally, these should be brief but descriptive of the change that occurred so you can later scan commit messages to get a feel for how the project evolved, and/or revisit a specific commit if needed (i.e., go back to an older version of a file).
Once we click the green “Commit changes to 3 files” button, we will have made a new commit to our repo (locally). Let’s create another commit (stage/commit)with the .gitignore file.
//WIP area is gone because committed all of the changes to our repo. However, any change that occurs to any file in our project folder that is not listed in the .gitignore will show up as having been modified since our last commit. This is quite powerful, and one of the main benefits of using git.
Now that we have committed our changes locally, we want to push them to the remote (i.e., github.com). On GitKraken, this just means clicking the “Push” button right above our commits. The first time we do this we’ll get a message asking what branch we want to push to. In most cases, the default here is what we want. We want our
main branch locally to push to our
main branch on the remote. After this is set it will not change unless you change it manually.
Shortly after pushing, you should get a message on GitKraken stating that you pushed sucessfully. Now, if you go to your repo online and refresh, you will see our changes.
There are a few things here that are incredibly helpful for not only collaboration, but conducting your work in a transparent and open way. First, you can see that not only are the files now posted on GitHub, but also all the commits we made. If we click on the commits (where the green arrow is pointing in the above picture) we can see all of the commits that have been made since the project began.
Each of these commits also link to the diff view, showing the difference between the file in that commit and its previous state.
In some ways, this may lead to feelings of vulnerability, particularly as you are learning new skills. However, it is worth noting that GitHub repositories are very rarely reviewed by anybody outside of the people managing it. But, working with a version control system also allows you to be open in your process, while also never losing work. You can always go back to previous versions of files and, as we’ll see with branching, this can often free you up to experiment without fear of “burning the house down”.
8.3.1 Merge conflicts
In this case, we are working on this repository alone. But let’s replicate what it would look like if an external collaborator made changes. We’ll do this by editing the README file directly on GitHub (by clicking the pencil icon on the README) and committing the changes there. This will put the remote branch ahead of our local branch.
If we go back to GitKraken, you will see that we now have two icons that are separated. The icon that says “main” with the GitHub logo is the main on the remote, while the icon that says “main” with the laptop logo is the main on our local. These are no longer in sync—our remote is ahead of our local by one commit. To get these changes to our local, we would need to pull (just click to pull button) and the README file on our local would (suddenly) look just like the one on GitHub.
Each time you start to work on your project, you should first pull for the most recent changes. This will help avoid merge conflicts. When you are done working for the day, or on that particular project, make sure you commit all of your changes and push them to the remote (so others can pull them down).
However, rather than pulling first (as we should) let’s make edits to the same file, on the same lines, commit those changes locally, and try to push them to the remote. This is the equivalent of two collaborators working on the same lines of the same file and both committing them to the repo, without first pulling the most recent changes.
On the remote, we made edits to the first and second lines. On our local, let’s make edits to the first and third lines, like so:
Then we’ll move to GitKraken as we normally would and stage the file (preparing to commit). Notice the diff shows the difference between our file and the previous verion on our local repo, which is different from the remote, because we forgot to pull first.
Not pictured in the above is that, before staging, had we first looked at our commits, we would have seen that our local was behind our remote. Regardless, in some cases, it might actually be worth creating a merge conflict intentionally and then decide what to keep (particularly because GitKraken has such a nice interface for dealing with merge conflicts).
Assuming that we were oblivious to the commits shown in GitKraken (and that we were behind the remote) we might try to just push our changes. In this case, we would get a message telling us our local was behind the remote. It would then ask us what we would like to do.
Generally, at this point we should either cancel, or “Pull (fast-forward if possible)”. The former will allow us to go back where we were, and even undo our commit. Using GitKraken it is very simple to undo commits that have not been pushed to the remote; you just click the “undo” button along the top. Things become more complicated once the commits have been pushed to the remote. Undoing commits at that point is still possible, but less straightforward (and will not be covered here; see however, here). Except in rare circumstances, you should almost always avoid using “Force Push”. While this may work, it is destructive and can erase the previous commits (indeed, it’s part of how you delete a commit after pushing). Pull fast-forward will pull the most recent changes, try to merge them in with your changes and, if successful, push all changes to the remote. This is generally a fairly safe option, although it is more safe to simply cancel and do the pull on your own first.
In this case, I tried “Pull (fast-forward if possible)” but, because I had made edits to a line that had already been edited, the automatic merge failed, which looked like the below.
So I have not actually pushed anything to the remote yet, because I have to resolve the merge conflict manually. My GitKraken windows now look like the below
At this point, I just have to click on the file in the “Conflicted Files” pane and I will enter GitKraken’s merge conflict editor.
The top portion of the above shows the two files, with the left pane showing (in this case) my local file and the right pane showing the file from the remote. I can click which file I want to keep, and I get a preview below. In this case, I’ve chosen to stick with what was on the remote. In the preview below, however, you see that part of the “A” file (from my local commit) shows in the preview (“And a different description”). This is because we asked git to merge automatically for us if it could. Line 3 on my local had changed, but this did not conflict with anything on the remote, so that change was merged in. This is an important distinction. Merge conflicts deal with conflicting lines of a file, not the file itself. If you ask the file to merge automatically, it will merge whatever it can and leave you to deal with what cannot be merged automatically, which may or may not be what you want. You could, of course, always edit the file again after dealing with the merge conflict.
After we have selected the lines we’d like to keep (including potentially both sets of changes) you click “Save” near the top right and the file is automatically staged.
A pre-slugged commit message is also provided, which I have changed in the above to be a bit more specific. Once you have resolved and committed the merge conflict, you can again push to the remote.
Notice in the above that it looks like separate tracks or branches right around the merge conflict. We’ll talk about branching (among other topics) in the next section.