Here’s something I tried out today. I don’t think it will work out in the long term, but I spent time on it so might as well make note of it here in case it helps someone else.
I have multiple machines – one at office, couple at home – and usually my workflow involves having whatever I am working on being present in a local folder (not in Dropbox) managed by Git, with changes pushed and pulled to GitHub. That works fine with the caveat that sometimes I forget to push the changes upstream and leave office/ home and and then I am unable to work at home/ office because my latest code is on the other machine while GitHub and the machine I am on don’t have those changes.
One solution would be to have a scheduled task (or some hook to PowerShell ISE) that automatically commits & pushes my changes to GitHub periodically (or whenever I click save). I haven’t explored those but they seem wasteful. I like my commits to be when I make them, not run automatically, resulting in lots of spurious entries in my history.
My idea is to have the working tree in Dropbox. This way Dropbox takes care of syncing my latest code across all my machines. I don’t want to move the Git repository into Dropbox for fear of corruption/ sync conflicts, so I’ll keep that out of Dropbox in a local folder on each machine. My revised workflow will be that I am on a particular machine, I’ll make some changes, maybe commit & push them … or maybe not, then I’ll go to the other machine and I can continue making changes there and push them off when I am happy. Or so I hope …
Setting it up
Consider the case of two machines. One’s GAIA, the other’s TINTIN. Let’s start by setting up a repository and working tree on GAIA.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
D:\Dropbox\Scripts>git init --separate-git-dir c:\Users\Rakhesh\Git\Test.git Test Initialized empty Git repository in c:/Users/Rakhesh/Git/Test.git/ D:\Dropbox\Scripts>cd Test D:\Dropbox\Scripts\Test>git remote add origin git@github.com:rakheshster/Test.git D:\Dropbox\Scripts\Test>echo "test" > blah.txt D:\Dropbox\Scripts\Test>git add blah.txt D:\Dropbox\Scripts\Test>git commit -m "First commit" [master (root-commit) 4fd5580] First commit 1 file changed, 1 insertion(+) create mode 100644 blah.txt D:\Dropbox\Scripts\Test>git push -u origin master Counting objects: 3, done. Writing objects: 100% (3/3), 219 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To git@github.com:rakheshster/Test.git * [new branch] master -> master Branch master set up to track remote branch master from origin. |
Straight-forward stuff. My working tree is in Dropbox, Git repository is in a local folder. I make a commit and push it to GitHub. So far so good.
Now I get to TINTIN.
Thanks to Dropbox TINTIN already has the working tree. But it doesn’t have the Git repository (as that’s not a part of Dropbox). So even though the working tree appears on TINTIN I can’t issue any Git commands against it.
1 2 |
D:\Dropbox\Scripts\Test>git status fatal: Not a git repository: c:/Users/Rakhesh/Git/Test.git |
The one time workaround is to pull the repository and working elsewhere and simply copy the repository to where the working tree in Dropbox expects it (c:/Users/Rakhesh/Git/Test.git
in the above example)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
C:\Temp>git clone git@github.com:rakheshster/Test.git Test Cloning into 'Test'... Warning: Permanently added the RSA host key for IP address '192.30.252.129' to the list of known hosts. remote: Counting objects: 3, done. remote: Total 3 (delta 0), reused 3 (delta 0) Receiving objects: 100% (3/3), done. Checking connectivity... done. C:\Temp>cd Test C:\Temp\Test>xcopy /E /H /K /I .git C:\Users\Rakhesh\Git\Test.git C:\Temp\Test>cd .. C:\Temp>rmdir /s /q Test |
Here’s what the xcopy
switches do:
/K
Copies attributes. Normal Xcopy will reset read-only attributes./E
Copies directories and subdirectories, including empty ones./H
Copies hidden and system files also./I
If destination does not exist and copying more than one file, assumes that destination must be a directory.
I use xcopy
because the Git repository is hidden and I can’t use move
. The various switches ensure xcopy
copies everything over.
Update: While writing this post I realized the above is an over-kill. All I need to do on TINTIN is the following:
1 |
C:\Users\Rakhesh\Git>git clone --bare git@github.com:rakheshster/Test.git |
Simple! This creates a bare-repository in the expected location.
Now I open the config
file in the Git repository and add a pointer to the working tree (not necessary, but is a good practice). That is, I add to the [core]
section:
1 |
worktree = D:/Dropbox/Scripts/Test |
Update2: If you create a bare repository as above remove from the [core]
section (if it exists):
1 |
bare = true |
Note: I had some trouble with the bare repository idea a couple of times so I wouldn’t recommend it. Specifically, it didn’t pick up some folder changes. If it doesn’t work for you, use the approach I went with initially.
Now TINTIN too is in sync with GAIA and GitHub.
1 2 3 4 5 |
D:\Dropbox\Scripts\Test>git status On branch master Your branch is up-to-date with 'origin/master'. nothing to commit, working directory clean |
Good!
Changing things
Let’s add a file and commit it on GAIA. And while we are at it push it to GitHub.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
D:\Dropbox\Scripts\Test>echo "def" >> blah.txt D:\Dropbox\Scripts\Test>git add blah.txt D:\Dropbox\Scripts\Test>git commit -m "Second commit" [master ab21cb9] Second commit 1 file changed, 1 insertion(+) D:\Dropbox\Scripts\Test>git push Counting objects: 5, done. Writing objects: 100% (3/3), 253 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To git@github.com:rakheshster/Test.git a68e729..ab21cb9 master -> master |
Once again, thanks to Dropbox TINTIN gets the changes in the working tree. But does it have the history? No! And because of that it will think there are changes to commit:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
D:\Dropbox\Scripts\Test>more blah.txt "test" "def" D:\Dropbox\Scripts\Test>git status On branch master Your branch is up-to-date with 'origin/master'. Changes not staged for commit: (use "git add ..." to update what will be committed) (use "git checkout -- ..." to discard changes in working directory) modified: blah.txt no changes added to commit (use "git add" and/or "git commit -a") |
I can’t simply commit this change on TINTIN and push it upstream because that would conflict with the server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
D:\Dropbox\Scripts\Test>git add blah.txt D:\Dropbox\Scripts\Test>git commit -m "Commit from TINTIN (will fail)" [master 4db4171] Commit from TINTIN (will fail) 1 file changed, 1 insertion(+) D:\Dropbox\Scripts\Test>git push To git@github.com:rakheshster/Test.git ! [rejected] master -> master (fetch first) error: failed to push some refs to 'git@github.com:rakheshster/Test.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. |
That’s because while both GAIA and TINTIN because from the same commit point, now GAIA (and upstream) are at commit A which is a descendant of the starting point, while TINTIN is at commit B which is also a descendant of the same starting point. A and B are siblings, so if the push were to succeed the previous commit and its history would be lost. (These are known as non-fast-forward updates and the git-push
help page has a diagram explaining this).
I can’t simply pull the changes from upstream either as the same conflict applies there too!
1 2 3 4 5 6 7 8 9 10 11 |
D:\Dropbox\Scripts\Test>git pull remote: Counting objects: 3, done. remote: Total 3 (delta 0), reused 3 (delta 0) Unpacking objects: 100% (3/3), done. From github.com:rakheshster/Test a68e729..ab21cb9 master -> origin/master Updating a68e729..ab21cb9 error: Your local changes to the following files would be overwritten by merge: blah.txt Please, commit your changes or stash them before you can merge. Aborting |
One workaround is to stash
the changes on TINTIN – which is fine in this case because they are not really changes, we have already captured it upstream and on GAIA – and then pull
the changes. (I could also commit
and then pull
changes from GitHub, merge
-ing them).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
D:\Dropbox\Scripts\Test>git stash Saved working directory and index state WIP on master: a68e729 First commit HEAD is now at a68e729 First commit D:\Dropbox\Scripts\Test>git pull Updating a68e729..ab21cb9 Fast-forward blah.txt | 1 + 1 file changed, 1 insertion(+) D:\Dropbox\Scripts\Test>git log commit ab21cb90234fa7445345601794fe83d1b7d24bae Author: Rakhesh Sasidharan <...@rakhesh.com> Date: Thu Feb 19 23:54:50 2015 +0400 Second commit commit a68e729845f873087b01af85146090028fed4bfd Author: Rakhesh Sasidharan <...@rakhesh.com> Date: Thu Feb 19 18:24:18 2015 +0400 First commit |
Note that TINTIN has the correct history too.
Update: If git pull
complains about changes it is also possible to do a git reset
as below:
1 2 3 4 5 6 |
git pull # this gives errors about conflicted files # you can also do git fetch --all # the latter will only fetch changes, won't try to merge and complain git reset --hard origin/master |
This is what I do nowadays. What git reset --hard
does is that it resets the working tree and HEAD
to the commit upstream (origin/master
), effectively replacing all the tracked local files with versions from upstream.
Changing things (part 2)
Now on to the scenario for which I am mixing Dropbox and GitHub. I make changes on GAIA and commit these, but forget to push them upstream.
1 2 3 4 5 6 7 8 9 |
D:\Dropbox\Scripts\Test>echo "ghi" >> blah.txt D:\Dropbox\Scripts\Test>git commit -m "Third commit" D:\Dropbox\Scripts\Test>git add blah.txt D:\Dropbox\Scripts\Test>git commit -m "Third commit" [master 6bc2105] Third commit 1 file changed, 1 insertion(+) |
When I get to TINTIN, the changes are there but once again it isn’t aware of the commit. And unlike in the previous case, it can’t pull any changes from GitHub as it too does not know of any changes. All I can do is add the changed file and commit it. Note that this will be a different commit from the one at GAIA (which TINTIN is not aware of).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
D:\Dropbox\Scripts\Test>more blah.txt "test" "def" "ghi" D:\Dropbox\Scripts\Test>git status On branch master Your branch is up-to-date with 'origin/master'. Changes not staged for commit: (use "git add ..." to update what will be committed) (use "git checkout -- ..." to discard changes in working directory) modified: blah.txt no changes added to commit (use "git add" and/or "git commit -a") D:\Dropbox\Scripts\Test>git add blah.txt D:\Dropbox\Scripts\Test>git commit -m "Third commit (from TINTIN)" [master 5ff7ced] Third commit (from TINTIN) 1 file changed, 1 insertion(+) |
And then I push it upstream when I am done working on TINTIN:
1 2 3 4 5 6 |
D:\Dropbox\Scripts\Test>git push Counting objects: 5, done. Writing objects: 100% (3/3), 272 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To git@github.com:rakheshster/Test.git ab21cb9..5ff7ced master -> master |
Back at GAIA the next day, when I check the status
I’ll see the following:
1 2 3 4 5 6 |
D:\Dropbox\Scripts\Test>git status On branch master Your branch is ahead of 'origin/master' by 1 commit. (use "git push" to publish your local commits) nothing to commit, working directory clean |
This is because as far as GAIA knows I had made a commit but didn’t push it to GitHub. So it tells me my local branch is ahead by 1 commit. Git never checks the remote end in real time when I issue a git status
. It merely reports based on the cached information it has.
I can’t of course push
this commit to GitHub because that has long moved on. We are back to a non-fast-forward update scenario here because GitHub has a commit from TINTIN which is a sibling to the commit from GAIA that we are now trying to push.
Thankfully, in this particular case I can do a git pull
and it will do a merge
of the commit from GAIA and GitHub. A merge
is possible here because the commits have a common ancestor and changes can be incorporated without any conflicts.
1 2 3 4 5 6 7 |
D:\Dropbox\Scripts\Test>git pull remote: Counting objects: 3, done. remote: Total 3 (delta 0), reused 3 (delta 0) Unpacking objects: 100% (3/3), done. From github.com:rakheshster/Test ab21cb9..5ff7ced master -> origin/master Merge made by the 'recursive' strategy. |
And just to confirm, GAIA has the whole history, including a commit indicating the merge
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
D:\Dropbox\Scripts\Test>git log commit 918a2fc73d230107f25820644664aed3c45549c5 Merge: 6bc2105 5ff7ced Author: Rakhesh Sasidharan <...@rakhesh.com> Date: Fri Feb 20 00:22:32 2015 +0400 Merge branch 'master' of github.com:rakheshster/Test commit 5ff7cedbb198761f57ca0d721c858e62e57503d7 Author: Rakhesh Sasidharan <...@rakhesh.com> Date: Fri Feb 20 00:17:23 2015 +0400 Third commit (from TINTIN) commit 6bc2105619cf71e6bbca511fcf31c04333c993a5 Author: Rakhesh Sasidharan <...@rakhesh.com> Date: Fri Feb 20 00:14:26 2015 +0400 Third commit commit ab21cb90234fa7445345601794fe83d1b7d24bae Author: Rakhesh Sasidharan <...@rakhesh.com> Date: Thu Feb 19 23:54:50 2015 +0400 Second commit commit a68e729845f873087b01af85146090028fed4bfd Author: Rakhesh Sasidharan <...@rakhesh.com> Date: Thu Feb 19 18:24:18 2015 +0400 First commit |
I can now push this to GitHub (and later pull
to TINTIN).
1 2 3 4 5 6 7 8 |
D:\Dropbox\Scripts\Test>git push Counting objects: 2, done. Delta compression using up to 8 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (2/2), 334 bytes | 0 bytes/s, done. Total 2 (delta 1), reused 0 (delta 0) To git@github.com:rakheshster/Test.git 5ff7ced..918a2fc master -> master |
That’s it!
Wrapping up
As you can see having your working tree in Dropbox, with the Git repository in a local folder elsewhere, is possible but involves some minor tweaking around. I am going to try this for some of my repositories going forward, and will update this post if I come across any gotchas.
Fringe cases
Occasionally when I am on one of my machines and do a git pull
I get the following:
1 2 3 4 5 6 7 8 |
D:\Dropbox\Scripts\PSScripts>git pull Updating 5f81434..f3f8e2c error: The following untracked working tree files would be overwritten by merge: Update-Hosts/Update-Hosts.ps1 Update-Hosts/install.bat Update-Hosts/schtask.xml Please move or remove them before you can merge. Aborting |
In the above case I had created a new folder (called Update-Hosts) on another machine and moved files to it. The machine I am currently on has these changes via Dropbox but isn’t aware of them from a Git point of view. So I did what Git suggests – remove this folder and git pull
again. I won’t lose anything because the remote copy has the latest changes anyway.
1 2 3 4 5 6 7 8 9 10 11 12 |
D:\Dropbox\Scripts\PSScripts>rmdir /s /q Update-Hosts D:\Dropbox\Scripts\PSScripts>git pull Updating 5f81434..f3f8e2c Fast-forward Update-Hosts.ps1 => Update-Hosts/Update-Hosts.ps1 | Bin 11782 -> 10060 bytes Update-Hosts/install.bat | 14 ++++++++++++++ Update-Hosts/schtask.xml | Bin 0 -> 5920 bytes 3 files changed, 14 insertions(+) rename Update-Hosts.ps1 => Update-Hosts/Update-Hosts.ps1 (85%) create mode 100644 Update-Hosts/install.bat create mode 100644 Update-Hosts/schtask.xml |
You can see it’s picked up the new directory and moves/ renames.