Git Workflow – Part 3: Remote Operations
Using Git on a single machine may have its uses, but most people using Git are using it for its remote operations. Git functions in a variety of ways, I’ll be focusing on a central repository in this article. For more complicated workflows, see the Git Book.
The first thing to do is to set up a central repository. You can make it a normal Git repository like any other, but a few things will be easier if you make it what’s called a bare repository. This means that there’s no working copy, it just stores revisions. Adding the flag “–bare” to the init command sets it up as bare. I also like to add the “–shared” flag if you’re going to be sharing it across multiple users, this helps fix permissions. Look it up if you’re very curious. It also helps to name the shared repository ending in “.git”. If it’s set up that way, when people clone it, Git will lose the extension and just take the name. We’ll see that below.
Kleinschs-Macbook:tmp nick$ mkdir first.git && cd first.git Kleinschs-Macbook:first.git nick$ git --bare init --shared Initialized empty shared Git repository in /Users/nick/tmp/first.git/ |
Now that the bare repository is set up, we need to get something into it. There are two ways to connect to a remote repository: cloning and adding to an existing repository. I’ll add it to an existing repository first. This can be handy if you want to import existing files into a new repository.
Kleinschs-Macbook:first.git nick$ cd .. Kleinschs-Macbook:tmp nick$ mkdir second.git && cd second.git Kleinschs-Macbook:second.git nick$ git init Initialized empty Git repository in /Users/nick/tmp/second.git/.git/ Kleinschs-Macbook:second.git nick$ echo 'First file contents' > file1 Kleinschs-Macbook:second.git nick$ git add file1 Kleinschs-Macbook:second.git nick$ git commit -am'Added file1' [master (root-commit) 1a603a8] Added file1 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 file1 |
Up to this point everything is standard. In order to commit changes into the central repository, we need to add the server and push the changes. The “remote” command takes care of remote repository setup in Git. The “remote add” adds a remote location, with the first parameter being the name and second being the location. In this example I’m using a local repository, but you could also do it via SSH, which I’ll show later. After you’ve added the remote location, use the “push” command to commit your changes to the remote branch. The first parameter is the remote location (using the name you just specified in the “remote add” command), second parameter is the remote branch for the commit. This pushes your current branch to the specified remote branch. For example, if I give the command “git push origin master”, it pushes my current branch to the master branch of the remote repository named origin. If you don’t want to push your current branch, you can specify which local branch to push in the command like this: “git push origin local-branch:remote-branch”. This would commit my branch named local-branch to the branch named remote-branch on origin.
Kleinschs-Macbook:second.git nick$ git remote add origin ../first.git Kleinschs-Macbook:second.git nick$ git push origin master Counting objects: 3, done. Writing objects: 100% (3/3), 239 bytes, done. Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To ../first.git * [new branch] master -> master |
Now that we’ve got an initial commit in, let’s try the second way of connecting to a remote repository. The command “clone” copies everything from an existing repository and sets it up as a new repository. Again, in this example I’m using a local directory as the argument, but it can take other arguments including SSH.
Kleinschs-Macbook:second.git nick$ cd .. Kleinschs-Macbook:tmp nick$ git clone first.git Initialized empty Git repository in /Users/nick/tmp/first/.git/ Kleinschs-Macbook:tmp nick$ cd first Kleinschs-Macbook:first nick$ ls file1 |
Notice that we cloned “first.git”, so our clone is just named “first”. Let’s try adding a file through the newly cloned branch.
Kleinschs-Macbook:first nick$ echo 'Second file contents' > file2 Kleinschs-Macbook:first nick$ git add file2 Kleinschs-Macbook:first nick$ git commit -am'added second file' [master 478a452] added second file 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 file2 Kleinschs-Macbook:first nick$ git status # On branch master # Your branch is ahead of 'origin/master' by 1 commit. # nothing to commit (working directory clean) |
If you compared the .git/config files between second.git and first (the cloned repository), you’ll notice that in the cloned repository, there’s an extra line about our master branch:
[branch "master"] remote = origin merge = refs/heads/master |
This sets up our local branch to link to origin’s master branch. This means that when we do remote operations from our master branch, we don’t have to specify the remote location or branch. This means that in order to push our changes, we just have to do a “git push”.
Kleinschs-Macbook:first nick$ git push Counting objects: 4, done. Delta compression using up to 2 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 300 bytes, done. Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /Users/nick/tmp/first.git 1a603a8..478a452 master -> master |
Now that we’re pushing changes out frequently, we need to be able to download those changes from the central repository. The “fetch” command gets all revisions from a remote repository.
Kleinschs-Macbook:first nick$ cd ../second.git Kleinschs-Macbook:second.git nick$ git fetch origin remote: Counting objects: 4, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From ../first 1a603a8..478a452 master -> origin/master |
You’ll see it downloaded them and refers to them using the notation “remote-location/remote-branch-name”. The fetch command doesn’t move the changes into your existing working copy, you’ll need to take care of this yourself. In order to pull the changes into your current local branch, you do a normal merge.
Kleinschs-Macbook:second.git nick$ git merge origin/master Updating 1a603a8..478a452 Fast forward file2 | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 file2 |
Since the process of fetching and merging is a frequent one, there’s a command that combines them: “pull”. Doing a pull runs a fetch and merges the specified remote branch into your current local one.
Kleinschs-Macbook:second.git nick$ git pull origin master From ../first * branch master -> FETCH_HEAD Already up-to-date. |
Pulling and pushing is fairly simple, but there’s one common error I’ve run into that I’ll illustrate here. I’m going to push a commit from my second repository, then try to push a commit from my cloned repository.
Kleinschs-Macbook:second.git nick$ echo 'Third file contents' > file3 Kleinschs-Macbook:second.git nick$ git add file3 Kleinschs-Macbook:second.git nick$ git commit -am'added third file' [master c114c2b] added third file 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 file3 Kleinschs-Macbook:second.git nick$ git push Counting objects: 4, done. Delta compression using up to 2 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 324 bytes, done. Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To ../first.git 478a452..c114c2b master -> master Kleinschs-Macbook:second.git nick$ cd ../first Kleinschs-Macbook:first nick$ echo 'Fourth file contents' > file4 Kleinschs-Macbook:first nick$ git add file4 Kleinschs-Macbook:first nick$ git commit -am'added fourth file' [master 17411ef] added fourth file 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 file4 Kleinschs-Macbook:first nick$ git push To /Users/nick/tmp/first.git ! [rejected] master -> master (non-fast forward) error: failed to push some refs to '/Users/nick/tmp/first.git' To prevent you from losing history, non-fast-forward updates were rejected. Merge the remote changes before pushing again. See 'non-fast forward' section of 'git push --help' for details. |
When I tried to do the second push, it was rejected. This is because Git requires that push operations result in a fast forward merge. If push operations didn’t have to be fast forward there could be unresolvable conflicts on the central server, which would leave things in a bad state for other users. For this reason, you need to take care of conflicts locally before doing a push. The simple fix for this is to do a pull and make sure you’re up to date before trying to do a push. If your changes really have to go in, there are ways to override the fast forward requirement, see the Git book for advanced options.
Kleinschs-Macbook:first nick$ git pull remote: Counting objects: 4, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From /Users/nick/tmp/first 478a452..c114c2b master -> origin/master Merge made by recursive. file3 | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 file3 Kleinschs-Macbook:first nick$ git push Counting objects: 7, done. Delta compression using up to 2 threads. Compressing objects: 100% (4/4), done. Writing objects: 100% (5/5), 533 bytes, done. Total 5 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (5/5), done. To /Users/nick/tmp/first.git c114c2b..5890924 master -> master |
Now let’s go back over to the second repository to make sure our changes went through correctly.
Kleinschs-Macbook:first nick$ cd ../second.git Kleinschs-Macbook:second.git nick$ git pull origin master remote: Counting objects: 7, done. remote: Compressing objects: 100% (4/4), done. remote: Total 5 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (5/5), done. From ../first * branch master -> FETCH_HEAD Updating c114c2b..5890924 Fast forward file4 | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 file4 Kleinschs-Macbook:second.git nick$ ls file1 file2 file3 file4 |
Everything looks good! I spoke above about setting up repositories over SSH. You’ll need to have a valid account on the machine to connect to a Git repository. It’s usually helpful to set up a public/private key so you don’t have to specify the password every time. There are other way to connect, but they’re a bit more involved. Google and the Git Book are your friend here (start by looking up Gitosis). Here’s an example of the connect string to use with SSH.
Kleinschs-Macbook:second.git nick$ git clone ssh://nick@kleinsch.com/var/git/kleinsch.com.git/ Initialized empty Git repository in /Users/nick/tmp/second.git/kleinsch.com/.git/ remote: Counting objects: 2181, done. ... |
I’ve made one mistake with SSH connect strings a couple of times now: this is not the same as the connect string for SCP. There’s no colon between the server and the path, unless you’re specifying a port. If you put one, you’ll just get a confusing error message, shown below.
Kleinschs-Macbook:second.git nick$ git clone ssh://nick@kleinsch.com:/var/git/kleinsch.com.git/ Initialized empty Git repository in /Users/nick/tmp/second.git/kleinsch.com/.git/ ssh: Could not resolve hostname kleinsch.com:: nodename nor servname provided, or not known fatal: The remote end hung up unexpectedly |
That covers the basics of remote operations. I’ll be doing one more post about Git focusing on remote branches.

I was looking for this! Thanks a lot. Which book do you suggest for git?