{"id":15424,"date":"2024-05-10T00:00:00","date_gmt":"2024-05-10T07:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/ise\/?p=15424"},"modified":"2024-07-18T11:58:25","modified_gmt":"2024-07-18T18:58:25","slug":"synchronizing-multiple-remote-git-repositories","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/ise\/synchronizing-multiple-remote-git-repositories\/","title":{"rendered":"Synchronizing multiple remote Git Repositories"},"content":{"rendered":"<p>In a recent project, we needed to synchronize multiple remote Git repositories. We were maintaining an open-source project on <a href=\"https:\/\/github.com\/\">GitHub<\/a>, and a private downstream mirror on <a href=\"https:\/\/azure.microsoft.com\/en-us\/products\/devops\/repos\/\">Azure Repos<\/a>. It was important for the project to have a safe and reliable process that would allow us to keep the two repositories in sync.<\/p>\n<p>In our specific case, this would be a one-way synchronization from GitHub to Azure Repos. It was not possible to synchronize in the other direction, as the Azure Repos repository would contain organization-specific implementations that could not be shared publicly.<\/p>\n<p>We made the conscious decision to keep the synchronization manual. The project maintainers wanted control over the process. They wanted to review, and approve, any changes that were being moved downstream. This would also give the team an opportunity to resolve any conflicts that may arise.<\/p>\n<p>During the project, we discovered different ways to approach this problem. In the end we settled on a process that was simple, and easy to follow &#8211; with minimal knowledge of Git required. This post will outline the process we followed, and the steps we took to synchronize changes between remote repositories.<\/p>\n<h2>Repository Setup<\/h2>\n<p>Before starting the synchronization process, we needed to set up the mirror on Azure Repos. This was a one-time operation, and was done using the Azure Repos web interface. This is a simple process, nicely documented <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/devops\/repos\/git\/import-git-repository?view=azure-devops#import-into-a-new-repo\">here<\/a>.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2024\/05\/import-repository.png\" alt=\"Importing the initial repository\" \/><\/p>\n<p><em>[Source: <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/devops\/repos\/git\/import-git-repository\">Microsoft Learn<\/a>]<\/em><\/p>\n<p>Once the repository was imported, we could setup our local repository to have two <em>remotes<\/em>. <code>origin<\/code> pointing to Azure Repos, and <code>upstream<\/code> pointing to GitHub.<\/p>\n<pre><code class=\"language-bash\">$ git clone &lt;azure-devops-url&gt; my-repo\r\n$ cd my-repo\r\n$ git remote -v\r\norigin      &lt;azure-devops-url&gt; (fetch)\r\norigin      &lt;azure-devops-url&gt; (push)\r\n$ git remote add upstream &lt;github-url&gt;<\/code><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2024\/05\/local-repository.png\" alt=\"Local Repository Setup\" \/><\/p>\n<p>At this point, our local repository was set up to push and pull changes from both <code>origin<\/code> and <code>upstream<\/code>. We could confirm this by running <code>git remote -v<\/code>:<\/p>\n<pre><code class=\"language-bash\">$ git remote -v\r\norigin      &lt;azure-devops-url&gt; (fetch)\r\norigin      &lt;azure-devops-url&gt; (push)\r\nupstream    &lt;github-url&gt; (fetch)\r\nupstream    &lt;github-url&gt; (push)<\/code><\/pre>\n<h3><strong>(Optional)<\/strong> Manually import a repo using git CLI<\/h3>\n<p>We were fortunate to be able to use the web interface to import the initial repository. However, if you need to import a repository using the <code>git<\/code> CLI, you can follow these steps:<\/p>\n<ol>\n<li>\n<p>Clone the source repo to a temporary folder on your computer using the <code>--bare<\/code> option:<\/p>\n<pre><code class=\"language-bash\">git clone --bare &lt;github-url&gt; bare-repo\r\ncd bare-repo<\/code><\/pre>\n<blockquote>\n<p><strong>Note:<\/strong> The <code>--bare<\/code> option creates a bare repository, which does not have a working directory. This is useful when you want to import a repository into another repository.<\/p>\n<\/blockquote>\n<\/li>\n<li>\n<p>Create a new empty repository on Azure Repos.<\/p>\n<\/li>\n<li>\n<p>Push the source repository to the new Azure Repos repository:<\/p>\n<pre><code class=\"language-bash\">git push --mirror &lt;azure-devops-url&gt;<\/code><\/pre>\n<blockquote>\n<p><strong>Note:<\/strong> The <code>--mirror<\/code> option pushes all branches, tags, and commits from the source repository to the target repository.<\/p>\n<\/blockquote>\n<\/li>\n<\/ol>\n<h2>Synchronizing Changes<\/h2>\n<p>The synchronization process itself was straightforward. We would fetch changes from GitHub, create a new local branch, and push it to Azure Repos. This would allow us to create a Pull Request to review the changes, and merge them into the <code>main<\/code> branch.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2024\/05\/sync-with-upstream.png\" alt=\"Sync with upstream\" \/><\/p>\n<p>Let&#8217;s assume we had some <em>updates<\/em> from <code>upstream<\/code> we wanted to synchronize. This is the process we would follow:<\/p>\n<ol>\n<li>\n<p>Fetch changes from GitHub (<code>upstream<\/code>):<\/p>\n<pre><code class=\"language-bash\">git fetch upstream<\/code><\/pre>\n<\/li>\n<li>\n<p>Checkout a new local branch, based on GitHub&#8217;s <code>main<\/code> branch:<\/p>\n<pre><code class=\"language-bash\">git checkout --no-track -b chore\/upstream-sync upstream\/main<\/code><\/pre>\n<blockquote>\n<p><strong>Note:<\/strong> The <code>--no-track<\/code> flag is used to create a new branch that is not tracking any remote branch.<\/p>\n<\/blockquote>\n<\/li>\n<li>\n<p>Merge the latest changes from Azure Repos (<code>origin<\/code>) into <code>chore\/upstream-sync<\/code>:<\/p>\n<pre><code class=\"language-bash\">git fetch origin\r\ngit merge origin\/main\r\n# resolve conflicts, if any<\/code><\/pre>\n<blockquote>\n<p><strong>Note:<\/strong> At this stage, if you receive an error message &#8220;fatal: refusing to merge unrelated histories&#8221;, it&#8217;s likely that <code>origin<\/code> did not originate from <code>upstream<\/code>. Ensure that the repository was <em>imported<\/em> correctly.<\/p>\n<\/blockquote>\n<\/li>\n<li>\n<p>Publish the new local branch to Azure Repos:<\/p>\n<pre><code class=\"language-bash\">git push -u origin chore\/upstream-sync<\/code><\/pre>\n<\/li>\n<li>\n<p>Create a <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/devops\/repos\/git\/pull-requests\">pull request<\/a> in Azure Repos to merge the <code>chore\/upstream-sync<\/code> branch into the <code>main<\/code> branch.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2024\/05\/new-pr-button.png\" alt=\"New PR Button\" \/><\/p>\n<\/li>\n<li>\n<p>After the PR meets all branch policies and has all required approvals, you can complete the PR.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2024\/05\/complete-pr-options.png\" alt=\"Complete PR Options\" \/><\/p>\n<\/li>\n<\/ol>\n<h2>&#8220;Patch-test&#8221; from <code>upstream<\/code><\/h2>\n<p>During the project, we discovered a scenario where we needed to test changes in the downstream environment before merging them into GitHub&#8217;s <code>main<\/code> branch. To achieve this, we needed to <em>patch<\/em> the changes from GitHub into a temporary branch on Azure Repos. This would allow us to run organization-specific tests, and deployment pipelines.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2024\/05\/patch-test-from-upstream.png\" alt=\"Patch-test from upstream\" \/><\/p>\n<p>This wasn&#8217;t a regular practice, but frequent enough to warrant a process:<\/p>\n<ol>\n<li>\n<p>Let&#8217;s assume we already have a feature branch, called <code>feat\/my-new-feature<\/code> on GitHub.<\/p>\n<\/li>\n<li>\n<p>Create a new local <code>patch-test\/<\/code> branch, based on the feature branch:<\/p>\n<pre><code class=\"language-bash\">git checkout --no-track -b patch-test\/my-new-feature upstream\/feat\/my-new-feature<\/code><\/pre>\n<\/li>\n<li>\n<p>Merge the latest changes from Azure Repos (<code>origin<\/code>) into <code>patch-test\/my-new-feature<\/code>:<\/p>\n<pre><code class=\"language-bash\">git fetch origin\r\ngit merge origin\/main<\/code><\/pre>\n<blockquote>\n<p>At this point, it&#8217;s important to re-iterate that our synchronization process was one-way. Once a branch was &#8220;contaminated&#8221; with organization-specific changes, it could not be merged, or pushed, back into GitHub.<\/p>\n<\/blockquote>\n<\/li>\n<li>\n<p>Push <code>patch-test\/my-new-feature<\/code> to Azure Repos (<code>origin<\/code>).<\/p>\n<pre><code class=\"language-bash\">git push -u origin patch-test\/my-new-feature<\/code><\/pre>\n<\/li>\n<li>\n<p>Run any necessary test and deployment pipelines on Azure DevOps from the <code>patch-test\/my-new-feature<\/code> branch.<\/p>\n<\/li>\n<\/ol>\n<p><strong>But wait! What if I need to make a change?!<\/strong><\/p>\n<ol>\n<li>\n<p>It is important to keep new <em>commits<\/em> isolated from the &#8220;contaminated&#8221; <code>patch-test\/<\/code> branch. We start by switching to the original feature branch, that is tracked to <code>upstream<\/code>:<\/p>\n<pre><code class=\"language-bash\">git checkout feat\/my-new-feature<\/code><\/pre>\n<\/li>\n<li>\n<p>Commit whatever change you need.<\/p>\n<\/li>\n<li>\n<p>Then, repeat the <em>merge<\/em> process:<\/p>\n<pre><code class=\"language-bash\">git checkout patch-test\/my-new-feature\r\ngit merge feat\/my-new-feature\r\ngit push<\/code><\/pre>\n<\/li>\n<li>\n<p>Once you are happy with the changes, you can push the feature branch to GitHub, and delete the <code>patch-test\/<\/code> branch.<\/p>\n<pre><code class=\"language-bash\">git checkout feat\/my-new-feature\r\ngit push\r\ngit branch -d patch-test\/my-new-feature<\/code><\/pre>\n<\/li>\n<\/ol>\n<h2>Conclusion<\/h2>\n<p>Synchronizing multiple remote Git repositories can be a daunting task. It certainly was for our team. However, the right process can provide a safe and reliable way to keep repositories in sync. With a few simple steps, maintainers and other users of the project, can confidently move open-source contributions downstream. Allowing them to maintain and update a private organization-specific implementation.<\/p>\n<p><em>Note: The feature image used in this blog was created using AI in <a href=\"https:\/\/designer.microsoft.com\">Microsoft Designer<\/a>.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>summary of the post<\/p>\n","protected":false},"author":158471,"featured_media":15427,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1,3451],"tags":[3520,3317,187],"class_list":["post-15424","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cse","category-ise","tag-azure-repos","tag-git","tag-github"],"acf":[],"blog_post_summary":"<p>summary of the post<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/15424","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/users\/158471"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/comments?post=15424"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/15424\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media\/15427"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media?parent=15424"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/categories?post=15424"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/tags?post=15424"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}