In my talk at Git Merge today, I mentioned that there were some other difficulties handling security issues in Git. Here's some more information:
I'm excited to be speaking at the Barcelona .NET Core group on Tuesday, 6 March, 2018. I'll be talking about How Microsoft "Got Git", adopting the Git version control system in Visual Studio and VSTS, and "Scripting Git", how to build .NET applications that interact with Git repositories using LibGit2Sharp.
Please join me! Especially if you're already in town for the Git Merge conference happening on the 7th and 8th of March. And if you're on the fence about coming to Git Merge, I can offer you 15% off your ticket, just because you seem like a nice person. When you register, just use the code:
I hope to see you there!
I get asked quite a lot whether I recommend a
merge-based workflow, or one
rebase onto master. But to be quite honest, I couldn't
possibly care less. Your workflow is your workflow after all, it's up
to your team to work in the way that's most productive for you.
For some teams that's merging, for some teams that's rebasing…
n the end, the code gets integrated and the end result is the same either
way, whether you merge or rebase it, right?
If you're a
rebase fan, you've probably run into cases where you get
conflicts during a
rebase that you wouldn't get during a
that's not very interesting… is there a case where
both finish and produce a result, but a different tree?
git-merge guaranteed to produce the same results as
It's actually not a guarantee; in fact, you can create two branches that
merge differently than they
rebase. To avoid any spoilers, I've hidden
the details in case you want to think about this on your own. 🤔 Click
"expand" below to see the details.
You can follow along with this GitHub repository.
Imagine that you have two branches, one is
master, and the other is the unimaginatively named
branchbranch. They're both based off a common ancestor
0d7088f. Further, imagine that your
branchhas two commits based off that common ancestor:
One One One Two 2 Two Three Three Three Four Four Four Five Five Five Six Six Six Seven Seven 7 Eight Eight Eight
Finally, imagine that your
masterbranch has a single commit based off the common ancestor:
One One Two 2 Three Three Four Four Five Five Six Six Seven Seven Eight Eight
What happens when you try to
When Git merges two branches, it only look at the tip commit in each branch, and compares them to their common ancestor. It does not look at any intermediate commits. In the above example, when we merge
master, the algorithm looks at the changes made in
branchby comparing commit
09d3ac4to the common ancestor commit
0d7088f. It also looks at the changes made in
masterby comparing commit
f2e864bto the common ancestor commit.
The merge algorithm compares each line1 in the common ancestor, comparing it to the file in
branchand the file in
master. If the line is unchanged in all branches, then there's no problem - that line is brought into the merge result. In this example, line 1 in unchanged in both branches, so line 1 of the merge result will be
If a line is changed in only one branch, then that change is brought forward into the merge result. In this example, line 7 is changed only in
branch. So in the resulting merge, line 7 will have the contents from
branch, which is the digit
7. Also, line 2 is changed only in
master, so in the merge result it will be the digit
Merge Result One 2 Three Four Five Six 7 Eight
Remember that merge only looks at the tip commits, so comparing the common ancestor to
branch, line two appears unchanged, since the ancestor and tip are identical.
Rebase works a bit differently - instead of doing a three-way merge between the tip commits on each branch, it tries to replay the commits on one branch onto another. In the above example, if we want to rebase
master, then Git will create a patch for each commit on
branchand apply those patches onto
When you rebase, Git will switch you to the
masterbranch, checking out
f2e864b. Then Git will apply the differences between the common ancestor and the first commit on branch. In this example, the patch between the common ancestor and the branch changes line two from
2. But that's already the value of the file in
master. So there's nothing to do, and the patch for
Then a patch for the second commit on the branch is applied: it changes like two back to the text representation, and changes line seven to a digit. So the rebase result is:
Rebase Result One Two Three Four Five Six 7 Eight
rebasepreserves the changes in the
mergepreserved the changes in
Generally these sorts of changes will cause a conflict instead of different results. It was key that in
branchwe changed the contents of line 2 back to the contents in the common ancestor. That allowed the merge engine to consider that the line in
Merge Result Rebase Result One One 2 Two Three Three Four Four Five Five Six Six 7 7 Eight Eight
So… is this a problem?
It might seem concerning that this comes up when there was an apparent revert of your changes. Logically, both the
masterbranches changed line two, but then
branchchanged it back. So although this seems rather derived, it's not that unlikely.
But whether you prefer a
mergeworkflow or a
rebaseworkflow, you should be careful of your integration and following good development practices:
Code review, ideally using pull requests, so that your team members have visibility into changes before they're integrated into
Continuous integration builds and tests, as part of your integration workflow. Ideally, with build policies to ensure that builds succeed and tests pass.
So make sure to do proper code reviews, which keep this an interesting difference instead of an actual problem in your workflow.
Strictly speaking, the merge engine doesn't actually look at lines, it looks at groups of lines, or "hunks". But it's easier to reason about individual lines for this example. ↩
rebasewill create and then apply patches, but when invoked with
git rebase --mergethen it will
cherry-pickthe changes. This uses the merge engine instead of patch application, but in this example, the results are the same. ↩
I like to pretend that for most of my career, I've only worked on Unix platforms. But that's not exactly true; in fact, I've spent most of my time building cross-platform applications. But I pretend because writing cross-platform applications is hard. And writing cross-platform UI applications is especially hard. So it's exciting to be able to use Electron to be able to easily create cross-platform UI.
I've started working on an Electron app, and even though it's not done yet, and definitely not ready to release, one of the things that I've been thinking about is packaging it for distribution.
Most Electron apps are bundled as ZIP archives, and maybe tarballs on
Linux. And that's fine but a really cool user experience is to provide
a disk image for macOS. Instead of just a zipped up
.app folder, this
lets you add some branding, and lets you add a link to the
folder so that you can just drag and drop:
One of the problems with creating a DMG, though, is that you need a Mac to do it. You can't easily create DMGs on a Linux box or a Windows VM. You need a proper Mac, and historically, it's been hard to find Macs in the cloud for CI hosts.
This was especially problematic if you needed to build any native code. You'd need Windows, Linux and Mac hosts in order to target all your platforms and, until recently, no CI system offered hosted build environments for all the platforms.
But now, Visual Studio Team Services offers hosted Windows, Linux and Mac systems for continuous integration build hosts. And they're free to get started with.
This means that you can use the macOS build hosts to package up your
into a nicely branded disk image.
Create a background image
One of the great things about using a DMG is that you can create a custom
background for the disk image. You can add your logo and provide some simple
instructions that people should drag and drop your app into the
If you're using Photoshop, make sure you set it up as a high DPI image. Here, I want a window size of 500x300, so I'm creating an image that is 1000x600 at 144dpi. If you do this, make sure you use the Save File dialog and select a PNG; the quick export functionality will save as 72dpi.
Create a DMG Generator Repository
Create a Git repository that contains the necessary bits to generate a DMG with all the artwork. You'll need to create a new repository that includes:
The background image that you created for you app.
The create-dmg program written by Andrey Tarantsov.
You can check this in directly, but I set this up as a submodule; the build step in VSTS can fetch submodules, even if they're hosted in other services. So you can have your proprietary software hosted in VSTS and it can reference Open Source submodules hosted in GitHub.
A shell script to execute
create-dmgwith all the appropriate arguments. The exact icon placement dimensions will depend on the size of your background and its layout. I named mine
build.sh; an example shell script for
App.app(using example DMG background, above) might look like:
#!/bin/sh create-dmg/create-dmg \ --volname "App" \ --background "background.png" \ --window-pos 200 120 \ --window-size 500 320 \ --icon-size 80 \ --icon "App.app" 125 175 \ --hide-extension "App.app" \ --app-drop-link 375 175 \ "App.dmg" \ "App.app"
(Remember to set your shell script executable.)
Set up the CI/CD pipeline
It's easy to set up a build pipeline for a hosted Mac build agent. In your Visual Studio Team Services account, navigate to the Build page, and select New Definition.
VSTS offers helpful build templates to get you started, but we're just running a shell script, so select empty process.
In the "Process" tab, give it a name like Generate DMG, and set the agent queue to Hosted macOS Preview.
In the "Get sources" tab, select where you've hosted the Git repository that contains your build script. Mine is - of course - hosted in a Git repository in Visual Studio Team Services, but the VSTS build process can build repositories that are hosted anywhere.
If you added
create-dmgas a submodule, then make sure to show "Advanced settings" and check the "Checkout submodules" option.
+button on build phase to add a new build task to Phase 1. In the task selection list, scroll or search for the "Command Line" task and click "Add".
Select the new task, and give it a display name like "Run Generator". In the "Tool" option, enter "
/bin/sh", since we wrote a shell script. Finally, in "Arguments", enter the name of your shell script, "
+button on the build phase to add another build task. In the task selection list, scroll or search for the "Publish Build Artifacts" task and click "Add".
This task will add the resultant DMG as a build artifact that we can download or deploy.
In the "Path to publish" option, enter the name of the DMG that your build script generated. In my example, this is "
Finally, in the "Artifact name" option, enter the name you want to save this artifact as; this is what you'll download or deploy. I recommend using the name of your DMG here.
And that's all - it's just a few simple steps to create a DMG.
Queue Your Build
Once you've configured your build, you can select "Save & queue" at the top of the page to save your build definition and queue the build to create a DMG.
When you do, you'll get a notification that your build has been queued. You can select the build number to follow a link to that build.
And once the build completes successfully, you'll be able to navigate to the "Artifacts" tab and download your DMG.
Obviously you could - and should - also deploy this to a download site automatically, creating a deployment pipeline in VSTS.
I created a build script that will download the latest version of Visual Studio Code, unzip it, and then bundle it up as a branded DMG. You can adapt it to your needs.
When you build this repository in the Visual Studio Team Services build process, you'll end up with a DMG that includes a branded Visual Studio Code experience:
git-open already had great support for GitHub, so I was already using it for my open source projects. It also supports BitBucket and GitLab. But I wanted to use it for my day job, where all my repositories are hosted in VSTS.
What is git-open?
git-open is a nifty tool that
will let you run a command on the command-line,
git open, to open up a
web browser to your Git repository hosting provider. This means that I can
run a single command, right from within my Git repository, and it will open up
a browser and navigate to my project in Visual Studio Team Services.
This is amazing because as much as I try to hang out in the command-line, that's not always useful for large projects. If I'm hacking on my (comparatively) little open source project, libgit2, it's easy enough to use ctags or to grep over the whole codebase. But for a big project that's not so easy.
What I really want is to use the great Roslyn-powered code search functionality in VSTS. So now I can open up a new browser pointing at VSTS without having to click around to get a new browser window open.
It will even look at the current branch that you're on and navigate there in the browser interface. This is perfect if you're starting your branch workflow by creating a new branch from a VSTS work item.
When I start my branch workflow this way, I've always got a branch on the
server that I fetch to my client. And then I can push my changes back and
eventually open a pull request. And
git open supports this workflow
beautifully, it will look at the branch that I have checked out locally
and try to navigate me there in the web browser. It can navigate to branches
in Git repositories hosted in several hosting providers, including VSTS and
But it's not just branches - it can also navigate to the work item hub.
This gives you a complete end-to-end branch workflow: create a new branch
from a work item in VSTS, pull it down, and then you can use
git open --issue
to navigate back to the VSTS work items in your browser.
To get started with git-open, all you have to do is
download the latest release, unzip it and then
git-open shell script somewhere in your path.
git has an extension model where all the commands you run - say
git pull -
are actually separate commands. They're not all just built in to the git
command itself. When you run
git pull, git goes looking for an executable
in your path named
So if you want to add your own Git commands, you can just put an executable
git-something into your path. When you run
git something, Git will
I have a directory in my home directory where I put useful shell scripts that I make sure is included in my path. On Unix systems this is
~/bin. On Windows machines, this is
I also manage all these scripts - along with my dotfiles - in a Git repository. So when I start working on a new machine, I can just
git clonemy dotfiles repository.