13
votes

On the builds server I have set up TeamCity (8.1.1) so that it executes the build process if there are changes in either the master, one of the feature branches or one of the pull request branches using the branch specifier:

+:refs/heads/*
+:refs/pull/(*/merge)

I have turned on the build agent option:

teamcity.git.use.local.mirrors=true

which clones the repository in a directory outside the build directory and then pulls from that local repository.

The build process needs access to the git repository and the master branch, even for builds of one of the feature branches or pull request branches. However TeamCity only has the branch that contains the changes in the local repository thereby making my builds fail, e.g. when the change was on the issue/mycoolissue branch then that is the only branch that exists in the git repository in the TeamCity working space.

I have tried performing a local git fetch to get the master branch but because the local repository does not have the master branch this fails. While I could add a remote pointing to the origin (a github private repository) that would mean that I'd have to handle credentials as well and I'd rather have TeamCity take care of all of that for me.

My question is whether there is a way to tell TeamCity to just pull all the branches into both the local repository and the working repository?

2
Downvoter any reason for the downvote? I'd be happy to improve my question if it makes no sense.Petrik

2 Answers

16
votes

Starting from TeamCity 10.0.4, you can do that by adding a configuration parameter teamcity.git.fetchAllHeads=true See here

0
votes

It turns out that (so far) there is no way to do this nicely in TeamCity so in the mean time this problem has been solved by running an additional MsBuild script at the start of the build process that verifies whether the master branch is present in the current (local) repository and getting it if it is not.

The script looks like:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
         DefaultTargets="Run"
         xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <DirWorkspace>$(MSBuildProjectDirectory)</DirWorkspace>
        <DirRepository Condition=" '$(DirRepository)' == '' ">$(DirWorkspace)</DirRepository>
        <DirGit Condition=" '$(DirGit)' == '' ">c:\Program Files (x86)\Git\bin</DirGit>      
    </PropertyGroup>

    <Import Project="$(DirWorkspace)\GitHasMasterBranch.msbuild"
            Condition="Exists('$(DirWorkspace)\GitHasMasterBranch.msbuild')"/>  
    <Import Project="$(DirWorkspace)\GitGetMasterBranch.msbuild"
            Condition="Exists('$(DirWorkspace)\GitGetMasterBranch.msbuild')"/>  

    <Target Name="Run" DependsOnTargets="_DisplayInfo;_FetchOriginMasterIfNotExists">
        <!-- Do nothing here -->
    </Target>

    <!-- Display info -->
    <Target Name="_DisplayInfo">
        <Message Text="Preparing workspace ..." />
    </Target>

    <PropertyGroup>
        <ExeGit>$(DirGit)\git.exe</ExeGit>
    </PropertyGroup>
    <Target Name="_FetchOriginMasterIfNotExists" DependsOnTargets="_DisplayInfo">
        <GitHasMasterBranch LocalPath="$(DirRepository)">
            <Output TaskParameter="HasMaster" PropertyName="HasMaster" />
        </GitHasMasterBranch>

        <Message Text="Not fetching master branch because it already exists" Condition="($(HasMaster))" />
        <Message Text="Fetching master branch because it does not exist" Condition="(!$(HasMaster))" />
        <GitGetMasterBranch LocalPath="$(DirRepository)" Condition="(!$(HasMaster))"/>
    </Target>
 </Project>

In this script the GitHasMasterBranch MsBuild inline script looks like:

<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' 
         ToolsVersion="4.0">
    <UsingTask TaskName="GitHasMasterBranch" 
               TaskFactory="CodeTaskFactory" 
               AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
        <ParameterGroup>
            <LocalPath ParameterType="System.String" Required="true" />
            <HasMaster ParameterType="System.Boolean" Output="true" />
        </ParameterGroup>
        <Task>
            <Code Type="Method" Language="cs">
                <![CDATA[
                    public override bool Execute()
                    {
                        var info = new System.Diagnostics.ProcessStartInfo
                                {
                                    FileName = "git",
                                    Arguments = "branch",
                                    WorkingDirectory = LocalPath,
                                    UseShellExecute = false,
                                    RedirectStandardOutput = true,
                                    RedirectStandardError = true,
                                };

                        var text = new System.Text.StringBuilder();
                        var process = new System.Diagnostics.Process();
                        process.StartInfo = info;
                        process.OutputDataReceived += 
                            (s, e) => 
                            { 
                                text.Append(e.Data);
                            };
                        process.ErrorDataReceived += 
                            (s, e) => 
                            { 
                                if (!string.IsNullOrWhiteSpace(e.Data))
                                {
                                    Log.LogError(e.Data); 
                                }
                            };
                        process.Start();

                        process.BeginOutputReadLine();
                        process.BeginErrorReadLine();
                        process.WaitForExit();

                        HasMaster = text.ToString().Contains("* master");

                        // Log.HasLoggedErrors is true if the task logged any errors -- even if they were logged 
                        // from a task's constructor or property setter. As long as this task is written to always log an error
                        // when it fails, we can reliably return HasLoggedErrors.
                        return !Log.HasLoggedErrors;
                    }
                ]]>  
            </Code>
        </Task>
    </UsingTask>
</Project>

And the GitGetMasterBranch MsBuild inline script looks like:

<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' 
         ToolsVersion="4.0">
    <UsingTask TaskName="GitGetMasterBranch" 
               TaskFactory="CodeTaskFactory" 
               AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
        <ParameterGroup>
            <LocalPath ParameterType="System.String" Required="true" />
        </ParameterGroup>
        <Task>
            <Code Type="Method" Language="cs">
                <![CDATA[
                    public override bool Execute()
                    {
                        // Get the name of the current branch
                        var info = new System.Diagnostics.ProcessStartInfo
                                {
                                    FileName = "git",
                                    Arguments = "symbolic-ref --short -q HEAD",
                                    WorkingDirectory = LocalPath,
                                    UseShellExecute = false,
                                    RedirectStandardOutput = true,
                                    RedirectStandardError = true,
                                };

                        var text = new System.Text.StringBuilder();
                        var process = new System.Diagnostics.Process();
                        process.StartInfo = info;
                        process.OutputDataReceived += 
                            (s, e) => 
                            { 
                                text.Append(e.Data);
                            };
                        process.Start();

                        process.BeginOutputReadLine();
                        process.BeginErrorReadLine();
                        process.WaitForExit();

                        var currentBranch = text.ToString().Trim();

                        // git fetch
                        info = new System.Diagnostics.ProcessStartInfo
                                {
                                    FileName = "git",
                                    Arguments = "fetch origin",
                                    WorkingDirectory = LocalPath,
                                    UseShellExecute = false,
                                    RedirectStandardOutput = true,
                                    RedirectStandardError = true,
                                };

                        process = new System.Diagnostics.Process();
                        process.StartInfo = info;
                        process.OutputDataReceived += 
                            (s, e) => 
                            { 
                                if (!string.IsNullOrWhiteSpace(e.Data))
                                {
                                    Log.LogMessage(MessageImportance.High, e.Data);
                                }
                            };
                        process.Start();

                        process.BeginOutputReadLine();
                        process.BeginErrorReadLine();
                        process.WaitForExit();

                        // git checkout master
                        info = new System.Diagnostics.ProcessStartInfo
                                {
                                    FileName = "git",
                                    Arguments = "checkout master",
                                    WorkingDirectory = LocalPath,
                                    UseShellExecute = false,
                                    RedirectStandardOutput = true,
                                    RedirectStandardError = true,
                                };

                        process = new System.Diagnostics.Process();
                        process.StartInfo = info;
                        process.OutputDataReceived += 
                            (s, e) => 
                            { 
                                if (!string.IsNullOrWhiteSpace(e.Data))
                                {
                                    Log.LogMessage(MessageImportance.High, e.Data);
                                }
                            };
                        process.Start();

                        process.BeginOutputReadLine();
                        process.BeginErrorReadLine();
                        process.WaitForExit();

                        // git pull
                        info = new System.Diagnostics.ProcessStartInfo
                                {
                                    FileName = "git",
                                    Arguments = "pull",
                                    WorkingDirectory = LocalPath,
                                    UseShellExecute = false,
                                    RedirectStandardOutput = true,
                                    RedirectStandardError = true,
                                };

                        process = new System.Diagnostics.Process();
                        process.StartInfo = info;
                        process.OutputDataReceived += 
                            (s, e) => 
                            { 
                                if (!string.IsNullOrWhiteSpace(e.Data))
                                {
                                    Log.LogMessage(MessageImportance.High, e.Data);
                                }
                            };
                        process.Start();

                        process.BeginOutputReadLine();
                        process.BeginErrorReadLine();
                        process.WaitForExit();

                        // git checkout <CURRENT_BRANCH>
                        info = new System.Diagnostics.ProcessStartInfo
                                {
                                    FileName = "git",
                                    Arguments = string.Format("checkout {0}", currentBranch),
                                    WorkingDirectory = LocalPath,
                                    UseShellExecute = false,
                                    RedirectStandardOutput = true,
                                    RedirectStandardError = true,
                                };

                        process = new System.Diagnostics.Process();
                        process.StartInfo = info;
                        process.OutputDataReceived += 
                            (s, e) => 
                            { 
                                if (!string.IsNullOrWhiteSpace(e.Data))
                                {
                                    Log.LogMessage(MessageImportance.High, e.Data);
                                }
                            };
                        process.Start();

                        process.BeginOutputReadLine();
                        process.BeginErrorReadLine();
                        process.WaitForExit();

                        // Log.HasLoggedErrors is true if the task logged any errors -- even if they were logged 
                        // from a task's constructor or property setter. As long as this task is written to always log an error
                        // when it fails, we can reliably return HasLoggedErrors.
                        return !Log.HasLoggedErrors;
                    }
                ]]>  
            </Code>
        </Task>
    </UsingTask>
</Project>

Essentially all this last script does is to store the current branch name, perform a GIT fetch to get all the branches, perform a GIT checkout of the master branch and then perform a GIT checkout of the original branch.

It's not the fastest approach but it works for now.