By the end of that initial tutorial, we had completed the following tasks:
- Created a local Git repository containing a Windows PowerShell script
- Created a cloud-based repository in GitHub
- Pushed our local repo to the cloud repo to make our code universally available
For today’s lesson, I’ll assume that you mastered the skills in Part 1. Now, we’ll broaden and deepen our new Git abilities, starting by pulling our cloud repo to another computer. We’ll then address the common situation in multi-developer scenarios of handling simultaneous pushes and merge conflict resolution. Shall we begin?
Pulling and merging code to another computer ^
One of the many beautiful aspects of distributed version control systems such as Git is that your source code is always available to you and potentially to other developers.
For instance, let’s imagine that I began our Get-SysInfo PowerShell project on my Windows 8.1–based work computer. Now I’m home, working on my Windows 10 computer, and I want to pull down our cloud-based GitHub repository to continue my development.
I’ll first install Git for Windows and start a new, elevated PowerShell console session. As we discussed in Part 1, I use git config to identify myself to Git with a username and an e-mail address.
Next, I’ll set the stage for pulling down my Get-Sysinfo public GitHub project by creating a local project folder named Get-SysInfo, changing our command prompt into that folder, and finally enabling Git tracking. Here are the three commands in sequence:
New-Item -Type Directory -Name Get-Sysinfo
We now need to add our GitHub repository as a remote. To that point, we need the GitHub remote URL. Navigate to the proper GitHub page and click the clipboard icon. I called out this icon on the following screenshot as A. We’ll concern ourselves with annotation B in the next installment of this series where we discuss project forking.
Our cloud-based GitHub project
We use git remote add to point our local Git client to a remote repository.
git remote add origin https://github.com/timothywarner/get-sysinfo.git
In the previous line of code, origin is simply the default name of the remote tracked repo. That remote repo doesn’t have to reside in GitHub; on a development team, you may add your colleague’s repos to your local instance as remotes as well. You can have more than one remote repo with Git; you’ll just need to specify another name for additional remotes or else you’ll get a name conflict error.
Okay, now it’s time for the “meat and potatoes” of the process. We use git pull to perform a simultaneous fetch and merge operation. The fetch copies the remote file(s) to our local repo, and the merge applies the remote’s changes to our local copies. In this case, however, we aren’t worried about merging because our local folder is empty.
git pull origin master
In the previous code, master refers to our GitHub project’s default branch, which is always named master. We’ll cover branching in a future lesson.
As you can see in the following screenshot, we now have the Get-Sysinfo.ps1 script on our local computer. Remember that .git is a hidden folder that contains the Git tracking database.
Our local copy of a remote repository
Now, then—those of you with prior experience with Git probably wondered why we didn’t run git clone. We run git clone when we want to copy some or all of a Git project to our local computer. Although we very well could have performed a clone just now, I elected for us to do a pull for two reasons:
- We’re pulling the GitHub repo into an empty directory, so we don’t have to worry about conflicts.
- In practice, you run clone just once to nab a remote repository, whereas you’ll run pull nearly every day, depending upon how active a developer you are.
Anyway, back to business. You know the old saying that “a picture is worth a thousand words.” To that point, I created the following illustration to help you get a handle on our development environment.
Our development environment
As you can see in the previous illustration, three entities now have identical copies of the Get-Sysinfo project. We use push and pull operations to send and receive our committed changes, respectively.
Pushing changes to the cloud repository ^
Now, we’re going to increase the complexity level of our development environment. Here’s the battle plan:
- On the Windows 10 box, we’ll add a second function to our Get-Sysinfo This second function, named Test-Domain, performs a simple domain vs. workgroup environment check. We’ll also modify the existing Get-DotNetVersion function (I gave you that source code in the previous article in this series).
- On the Windows 8.1 box, we’ll intentionally create a conflicting change in the Get-DotNetVersion
- We’ll simultaneously push changes to GitHub and resolve any merge conflicts that arise (believe me, they will arise!).
The following screenshot shows the Windows 10 computer’s Get-Sysinfo script source code. I highlighted the new and changed parts:
Get-Sysinfo from the Windows 10 computer’s perspective
As seen in the previous Windows PowerShell ISE screen grab, I changed the Write-Host foreground color for the Get-DotNetVersion function. I also added the Test-Domain function.
I intentionally created a big-time conflict in the Windows 8.1 computer’s copy of the script. As you can see in the following screenshot, I actually reverted the Get-DotNetVersion function back into static code:
Get-Sysinfo from the Windows 8.1 computer’s perspective
When GitHub attempts to merge these incoming copies of Get-Sysinfo.ps1, what will happen? We can reasonably assume that the new Test-Domain function should merge cleanly because the current GitHub file doesn’t have it. But what about the changes to the Get-DotNetVersion function? Inquiring minds want to know…
We already know how to get the status of our local repository (git status). We also know how to add files to the Git tracking index (git add). Let’s go ahead and perform those actions, and then submit commits.
On the Windows 10 box:
git commit -m "Added the Test-Domain function."
On the Windows 8.1 computer:
git commit -m "Reverted the function back to static code."
Finally, we’ll push the two local repos to GitHub by running the following command on each computer:
git push origin master
Because we are the only user/admin of the remote repository, we authenticate with the same GitHub credentials on both computers.
Viewing merges and merge errors ^
Let’s now open the GitHub project and click the Latest commit link to see what’s changed in GitHub’s copy of the Get-Sysinfo.ps1 PowerShell script file.
Viewing the results of our latest commit in our GitHub cloud repo
- A: This was our original commit message on the Windows 10 computer.
- B: GitHub picked up our change to the Write-Host background color; so far, so good!
- C: We see our Test-Domain Again, this was totally expected behavior.
Whoops—we see some errors on our Windows 8.1 computer. What the heck happened with our Windows 8.1–based push to GitHub?
To investigate, we’ll return to the PowerShell console on the Windows 8.1 box and read the output. I put the most important parts in boldface:
PS C:\Users\Tim\get-sysinfo> git push origin master
Username for 'https://github.com': email@example.com
Password for 'https://firstname.lastname@example.org@github.com':
! [rejected] master -> master (fetch first)
error: failed to push some refs to 'https://github.com/timothywarner/get-sysinfo.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
The error output tells us that, in order to preserve the integrity of the GitHub remote repository, the Windows 8.1 computer’s Git client’s push request was rejected by the server.
Specifically, GitHub received the push from my Windows 10 computer before the push from the Windows 8.1 box. Thus, Git notifies us that we should pull those changes from the cloud to our local repo, and then attempt the push again.
Let’s follow Git’s instructions and perform a pull from the Windows 8.1 computer. Once again, I put the important output in bold:
PS C:\Users\Tim\get-sysinfo> git pull origin master
warning: no common commits
remote: Counting objects: 9, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 9 (delta 2), reused 8 (delta 1), pack-reused 0
Unpacking objects: 100% (9/9), done.
* branch master -> FETCH_HEAD
* [new branch] master -> origin/master
CONFLICT (add/add): Merge conflict in get-sysinfo.ps1
Automatic merge failed; fix conflicts and then commit the result.
The error is self-explanatory: The incoming copy of get-Sysinfo.ps1 conflicts with the state of the local file on the Windows 8.1 computer. We have ourselves a merge conflict, my friend!
Resolving merge conflicts ^
Let’s run git diff on our Windows 8.1 workstation to see specifically how our local source code conflicts with the GitHub-current version.
Comparing the local and remote files for conflict resolution purposes
By the way, press q to return to your PowerShell console prompt from the git diff output.
Once again, from the “picture is worth a thousand words” files, let me illustrate our code merge conflict situation graphically:
Merge conflicts are a way of life with Git-based development.
- A: We successfully pushed our changes from the Windows 10 computer to GitHub and merged them with the cloud-based copy of the script.
- B: Because the state of the GitHub repo has changed since the Windows 8.1 computer’s last push, the push fails.
- C: When we pull changes from GitHub to the Windows 8.1 computer’s local repo, we’re faced with manually resolving conflicts between the two versions of the script.
Well, in this case, it’s pretty easy to see the difference between the local and remote versions of the Get-Sysinfo.ps1 script file. Specifically, we need to decide whether we want the refactored Get-DotNetVersion function or if we want the static code.
I’ll tell you what—instead of manually resolving the conflict by copying the Get-DotNetVersion function and pasting it into our local file, we’re going to roll out the “big guns” and do a hard overwrite of our local repo. You need to be careful with the following technique because it deletes all local repo content and replaces it with the remote repo’s contents.
To accomplish this goal, we’ll first perform a git fetch. Recall that a fetch copies the remote file(s) to the local repo but doesn’t perform a merge.
git fetch origin master
Now we’ll invoke git reset. We normally use git reset to remove tracked files from the Git index. In this case, we use the --hard switch parameter to perform a hard overwrite.
git reset –-hard origin/master
We’ll observe that the Windows 8.1 computer’s local get-sysinfo.ps1 file now matches its online counterpart exactly.
The “take-home message” here is that, although Git does a great job of seamlessly merging code from multiple file versions, as a developer you need to intervene manually from time to time to sort out conflicts.
After-action review ^
If you’ve practiced everything I taught you in this and the previous installment of this series, then you are well on your way to mastering Git. As usual, I’ll leave you with some hand-picked resources to aid your further study.
- Git Basics – Working with Remotes
- The git pull Command
- Resolving a merge conflict from the command line
- GitHowTo – Resolving Conflicts