diff --git a/project/UnitTests/Core/SourceControl/GitTest.cs b/project/UnitTests/Core/SourceControl/GitTest.cs index e98b843c9..df1a1ffe4 100644 --- a/project/UnitTests/Core/SourceControl/GitTest.cs +++ b/project/UnitTests/Core/SourceControl/GitTest.cs @@ -76,6 +76,7 @@ public void PopulateFromFullySpecifiedXml() {0} Max Mustermann max.mustermann@gmx.de + v1.0.2.0 "; git = (Git)NetReflector.Read(xml); @@ -92,7 +93,8 @@ public void PopulateFromFullySpecifiedXml() Assert.AreEqual("max.mustermann@gmx.de", git.CommitterEMail, "#B11"); Assert.AreEqual(true, git.CommitBuildModifications, "#B12"); Assert.AreEqual(true, git.CommitUntrackedFiles, "#B13"); - } + Assert.AreEqual("v1.0.2.0", git.Revision, "#B14"); + } [Test] public void PopulateFromMinimallySpecifiedXml() @@ -115,7 +117,8 @@ public void PopulateFromMinimallySpecifiedXml() Assert.AreEqual(null, git.CommitterEMail, "#C11"); Assert.AreEqual(false, git.CommitBuildModifications, "#C12"); Assert.AreEqual(false, git.CommitUntrackedFiles, "#C13"); - } + Assert.AreEqual(null, git.Revision, "#C14"); + } [Test] public void ShouldApplyLabelIfTagOnSuccessTrue() diff --git a/project/core/sourcecontrol/Git.cs b/project/core/sourcecontrol/Git.cs index 0b672d035..d6b7700e6 100644 --- a/project/core/sourcecontrol/Git.cs +++ b/project/core/sourcecontrol/Git.cs @@ -27,6 +27,7 @@ namespace ThoughtWorks.CruiseControl.Core.Sourcecontrol /// <sourcecontrol type="git"> /// <repository>git://github.com/rails/rails.git</repository> /// <branch>master</branch> + /// <revision>v1.0.2.0</revision> /// <autoGetSource>true</autoGetSource> /// <fetchSubmodules>true</fetchSubmodules> /// <executable>git</executable> @@ -65,9 +66,11 @@ namespace ThoughtWorks.CruiseControl.Core.Sourcecontrol /// /// Once the repository is initialized the "git fetch origin" command is issued to fetch the remote changes. Next, /// "git log $LastIntegrationCommit..origin/$BranchName --name-status -c", + /// or "git log $LastIntegrationCommit..$Revision --name-status -c" (if 'revision' property was specified) /// is issued to get a list of commits and their changes, where $LastIntegrationCommit is the commit which was /// checked out the last time an integration was run. If the project has not yet been integrated, a /// "git log origin/$BranchName --name-status -c" + /// or "git log origin/$BranchName --name-status -c" (if 'revision' property was specified) /// command is issued instead. /// /// @@ -75,7 +78,8 @@ namespace ThoughtWorks.CruiseControl.Core.Sourcecontrol /// /// /// Once Cruise Control.NET has modifications detected and the 'autoGetSource' property is set to 'true' the "git checkout -f - /// origin/$NameOfTheBranch" command is issued. Also the "git clean -f -d -x" command to get a clean working copy to start a new build. + /// origin/$BranchName" (or "git checkout -f $Revision") command is issued. + /// Also the "git clean -f -d -x" command to get a clean working copy to start a new build. /// If 'fetchSubmodules' is set to 'true' git submodules will be fetched and updated. /// /// @@ -131,7 +135,7 @@ public class Git : ProcessSourceControl private BuildProgressInformation _buildProgressInformation; /// - /// Whether to fetch the updates from the repository and checkout the branch for a particular build. + /// Whether to fetch the updates from the repository and checkout the branch / revision for a particular build. /// /// 1.5 /// true @@ -237,6 +241,14 @@ public class Git : ProcessSourceControl [ReflectorProperty("workingDirectory", Required = false)] public string WorkingDirectory { get; set; } + /// + /// Repository revision (commit hash or tag name) to checkout and build. + /// + /// 1.9 + /// none + [ReflectorProperty("revision", Required = false)] + public string Revision { get; set; } + /// /// Initializes a new instance of the class. /// @@ -280,17 +292,17 @@ public override Modification[] GetModifications(IIntegrationResult from, IIntegr string lastCommit; if (revisionData.TryGetValue(COMMIT_KEY, out lastCommit)) { - logResult = GitLogHistory(Branch, lastCommit, to); + logResult = GitLogHistory(GetBranchNameOrRevision(Branch, Revision), lastCommit, to); } else { - Log.Debug(string.Concat("[Git] last integrated commit not found, using all ancestors of origin/", - Branch, " as the set of modifications.")); - logResult = GitLogHistory(Branch, to); + Log.Debug(string.Concat("[Git] last integrated commit not found, using all ancestors of ", + GetBranchNameOrRevision(Branch, Revision), " as the set of modifications.")); + logResult = GitLogHistory(GetBranchNameOrRevision(Branch, Revision), to); } // Get the hash of the origin head, and store it against the integration result. - string originHeadHash = GitLogOriginHash(Branch, to); + string originHeadHash = GitLogOriginHash(GetBranchNameOrRevision(Branch, Revision), to); revisionData[COMMIT_KEY] = originHeadHash; to.SourceControlData.Clear(); NameValuePair.Copy(revisionData, to.SourceControlData); @@ -308,8 +320,8 @@ public override void GetSource(IIntegrationResult result) if (!AutoGetSource) return; - // checkout remote branch - GitCheckoutRemoteBranch(Branch, result); + // checkout revision or remote branch + GitCheckoutRemoteBranch(GetBranchNameOrRevision(Branch, Revision), result); // update submodules if (FetchSubmodules) @@ -470,39 +482,39 @@ private ProcessInfo NewProcessInfo(string args, IIntegrationResult result, Proce /// /// Get the hash of the latest commit in the remote repository. /// - /// Name of the branch. + /// Name of the branch or revision /// IIntegrationResult of the current build. - private string GitLogOriginHash(string branchName, IIntegrationResult result) + private string GitLogOriginHash(string branchNameOrRevision, IIntegrationResult result) { ProcessArgumentBuilder buffer = new ProcessArgumentBuilder(); buffer.AddArgument("log"); - buffer.AddArgument(string.Concat("origin/", branchName)); + buffer.AddArgument(branchNameOrRevision); buffer.AddArgument("-1"); buffer.AddArgument("--pretty=format:\"%H\""); return Execute(NewProcessInfo(buffer.ToString(), result)).StandardOutput.Trim(); } /// - /// Get the commit history including changes between and origin/ + /// Get the commit history including changes between and /// - /// Name of the branch. + /// Name of the branch or revision /// The commit from which to start logging. /// IIntegrationResult of the current build. /// Result of the "git log" command. - private ProcessResult GitLogHistory(string branchName, string from, IIntegrationResult to) + private ProcessResult GitLogHistory(string branchNameOrRevision, string from, IIntegrationResult to) { ProcessArgumentBuilder buffer = new ProcessArgumentBuilder(); buffer.AddArgument("log"); - buffer.AddArgument(string.Concat(from, "..origin/", branchName)); - AppendLogOptions(buffer); + buffer.AddArgument(from + ".." + branchNameOrRevision); + AppendLogOptions(buffer); return Execute(NewProcessInfo(buffer.ToString(), to)); } - private ProcessResult GitLogHistory(string branchName, IIntegrationResult to) + private ProcessResult GitLogHistory(string branchNameOrRevision, IIntegrationResult to) { ProcessArgumentBuilder buffer = new ProcessArgumentBuilder(); buffer.AddArgument("log"); - buffer.AddArgument(string.Concat("origin/", branchName)); + buffer.AddArgument(branchNameOrRevision); AppendLogOptions(buffer); return Execute(NewProcessInfo(buffer.ToString(), to)); } @@ -603,18 +615,29 @@ private void GitFetch(IIntegrationResult result) ProcessExecutor.ProcessOutput -= ProcessExecutor_ProcessOutput; } + /// + /// Get the target name, either a revision or a remote branch name + /// + /// remote branch name + /// optional revision (may be null or empty) + /// + private string GetBranchNameOrRevision(string branchName, string revision) + { + return string.IsNullOrEmpty(revision) ? string.Concat("origin/", branchName) : revision; + } + /// - /// Checkout a remote branch with the "git checkout -q -f 'origin/branchName'" command. + /// Checkout a remote branch or revision with the "git checkout -q -f 'origin/branchName'" or "git checkout -q -f 'revision'" command. /// - /// Name of the branch to checkout. + /// Name of the branch to checkout. /// IIntegrationResult of the current build. - private void GitCheckoutRemoteBranch(string branchName, IIntegrationResult result) + private void GitCheckoutRemoteBranch(string branchOrRevision, IIntegrationResult result) { ProcessArgumentBuilder buffer = new ProcessArgumentBuilder(); buffer.AddArgument("checkout"); buffer.AddArgument("-q"); buffer.AddArgument("-f"); - buffer.AddArgument(string.Concat("origin/", branchName)); + buffer.AddArgument(branchOrRevision); // initialize progress information var bpi = GetBuildProgressInformation(result);