gitsynchronize

how do I keep two checked out git repositories synchronize with each other?


I have all of my data on my home PC in a git repo.

This home PC (and therefore the git repo) is only accessible from the local LAN that the home PC is on. Putting the git repo on an internet-accessible cloud server, or making my home PC accessible on the internet, is not an option. This limitation is important to mention for the below question, because otherwise the obvious solution is "just put your git repo on a cloud server".

I have a laptop I travel with called Laptop-A. Before I leave on my travels, I check out the repo on Laptop-A from my home PC by using: git clone ssh://main-PC/... During my travels, I make local changes on Laptop-A, and I commit them locally, but I can't push them since the main repo on my home PC is not accessible via the internet. My changes can only be pushed when return home and am on the same LAN as my home PC.

This all works, but now I'm adding Laptop-B while traveling. This laptop also has the main repo cloned, just like Laptop-A does. In fact, the two laptops are basically identical but they are two separate machines. I'm doing this in case one of the laptops has a hardware malfunction. In that case, I still have the other laptop.

However, now I run into the problem that for the two laptops to truly be redundant, when I make a local change to a git repo on one laptop, that change is not reflected in the git repo on the other laptop, since they are obviously two separately checked out copies of the repo. I could commit each change manually and separately on each laptop, but that is awkward and I could make a mistake leading to inconsistencies. Even if I do it correctly, it's annoying and will lead to merge conflicts once I want to push from both laptops when I return home.

What I want to do is commit a change on Laptop-A and then have this synced to Laptop-B, so that I can push (to the main repo) from either laptop once I return home.

How can I accomplish this?


Solution

  • No Git repository is "special" or "more important" than any other Git repository, unless you deem it so yourself.

    When you git clone ssh://main-PC/... on Laptop A, the Git you run on Laptop A creates a Git repository that is a peer of the Git on main-PC. You only think of the main PC version as "more master-y" because that's how you use it, and in fact, while traveling, the laptop version is "more master-y" because it gets new commits.

    Your laptop-A repository uses the name origin to remember the URL ssh://main-PC/.... The name origin is a remote. Your laptop-A Git uses remote-tracking names of the form origin/* to remember branch names in that other Git.

    On your laptop-B, you can either clone your laptop-A clone, or your main-PC clone. Your laptop-B will remember whatever URL you use here under its name origin, and use remote-tracking names origin/* to remember branch names copied during the clone process.

    You can add more remote names to either laptop. For instance, assuming laptops A and B both ran git clone ssh://main-PC/..., so that on both, origin means ssh://main-PC/..., you might want to do this on laptop A:

    git remote add laptop-b ssh://ip.address/path/to/repo.git
    

    for instance (of course the IP address may change over time, so you'll need to remove and re-add, or use git remote set-url, to fix it sometimes). Now laptop-A has a name for laptop-B, and you can git fetch or git push from A to B to obtain (fetch) or send (push) any commits that are on B but not A (fetch) or A but not B (push). Similarly, on laptop B:

    git remote add laptop-a ssh://ip.address/path/to/repo.git
    

    will create the remote name laptop-a on laptop B, and now you can git fetch or git push using this name.

    Commits that are on one laptop are now easily transferred to the other. Note that the remote-tracking name, laptop-b/master on laptop A for instance, is how the one laptop will remember that the other laptop has some commit—so now you'll want to have whichever laptop is "behind" update its local branch name(s) to remember the new commit(s).

    The key notion is that all three repositories are peers. None has any special status, except insofar as you personally decide that one has some special status. Instead of making a static decision, you can decide based on commit hash IDs as recorded under remote-tracking names like origin/master, laptop-a/master, and laptop-b/master.

    The branch names in each of these peer repositories are different. The commit hash IDs are the same, as long as they all have the same commits. When one repository lacks some commit(s), git fetch will yank them in (from the repository that needs the commits, to the one that has them), and/or git push will send them (from the repository that has the commits, to the one that needs them).

    These two operations—fetch and push—are almost symmetric, except for one other key difference. The fetch operation takes the source repository's branch names and renames them, so that they come in under remote-tracking names like origin/master. The push operation sends the commits, then either:

    Because the target of a push is setting its branch names, several special conditions hold:

    1. The branch must not be checked out into a work-tree. This works well if the repository is one made with --bare as it never has any branch checked out into a work-tree.
    2. Or, the branch can be checked out into a work-tree, but then the Git receiving the push must be told to allow such push operations (and other conditions must hold). See, e.g., Git receive.denyCurrentBranch updateInstead fails [Edit: or your better link, What is this Git warning message when pushing changes to a remote repository?.

    For these reasons, it's sometimes nicer to use an all-fetch work-flow; but then you have to use a second Git command to combine the new commits into the local repository. Many people like to use the git pull command for this, as it actually runs git fetch first, then runs a second Git command to incorporate the fetched commits.