Skip to content

Commit 1b7df3d

Browse files
committed
Adding GitRepoPublisher.
1 parent 27be676 commit 1b7df3d

File tree

1 file changed

+211
-0
lines changed

1 file changed

+211
-0
lines changed
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details.
2+
3+
using JetBrains.Annotations;
4+
using PostSharp.Engineering.BuildTools.Build.Model;
5+
using PostSharp.Engineering.BuildTools.Utilities;
6+
using System;
7+
using System.IO;
8+
using System.IO.Compression;
9+
using System.Linq;
10+
11+
namespace PostSharp.Engineering.BuildTools.Build.Publishing;
12+
13+
/// <summary>
14+
/// Publishes a zip file to a git repository by cloning the repo, replacing its content with the zip file content,
15+
/// then committing and pushing.
16+
/// </summary>
17+
[PublicAPI]
18+
public class GitRepoPublisher : ArtifactPublisher
19+
{
20+
private readonly string _gitHubUrl;
21+
private readonly string _commitMessage;
22+
23+
/// <summary>
24+
/// Initializes a new instance of the <see cref="GitRepoPublisher"/> class.
25+
/// </summary>
26+
/// <param name="files">The pattern matching the zip file(s) to publish.</param>
27+
/// <param name="gitHubUrl">The GitHub repository URL to publish to.</param>
28+
/// <param name="commitMessage">The commit message to use when pushing changes.</param>
29+
public GitRepoPublisher( Pattern files, string gitHubUrl, string commitMessage ) : base( files )
30+
{
31+
this._gitHubUrl = gitHubUrl;
32+
this._commitMessage = commitMessage;
33+
}
34+
35+
public override SuccessCode PublishFile(
36+
BuildContext context,
37+
PublishSettings settings,
38+
string file,
39+
BuildArguments buildArguments,
40+
BuildConfigurationInfo configuration )
41+
{
42+
var console = context.Console;
43+
44+
console.WriteMessage( $"Publishing '{file}' to git repository '{this._gitHubUrl}'." );
45+
46+
// Expand environment variables in the URL and commit message.
47+
var gitHubUrl = Environment.ExpandEnvironmentVariables( this._gitHubUrl );
48+
var commitMessage = Environment.ExpandEnvironmentVariables( this._commitMessage );
49+
50+
if ( string.IsNullOrEmpty( gitHubUrl ) )
51+
{
52+
console.WriteError( "The GitHub URL is empty or the environment variable is not defined." );
53+
54+
return SuccessCode.Fatal;
55+
}
56+
57+
// Create a temporary directory for cloning.
58+
var tempDirectory = Path.Combine( Path.GetTempPath(), $"GitRepoPublisher_{Guid.NewGuid():N}" );
59+
60+
try
61+
{
62+
Directory.CreateDirectory( tempDirectory );
63+
64+
// Clone the repository (shallow clone of default branch).
65+
console.WriteImportantMessage( $"Cloning repository '{gitHubUrl}' to '{tempDirectory}'." );
66+
67+
if ( !ToolInvocationHelper.InvokeTool(
68+
console,
69+
"git",
70+
$"clone --depth 1 \"{gitHubUrl}\" .",
71+
tempDirectory ) )
72+
{
73+
console.WriteError( "Failed to clone the repository." );
74+
75+
return SuccessCode.Fatal;
76+
}
77+
78+
// Delete all files in the repo except .git directory.
79+
console.WriteMessage( "Removing existing content from the repository." );
80+
81+
foreach ( var entry in Directory.EnumerateFileSystemEntries( tempDirectory ) )
82+
{
83+
var name = Path.GetFileName( entry );
84+
85+
if ( name.Equals( ".git", StringComparison.OrdinalIgnoreCase ) )
86+
{
87+
continue;
88+
}
89+
90+
if ( Directory.Exists( entry ) )
91+
{
92+
Directory.Delete( entry, true );
93+
}
94+
else
95+
{
96+
File.Delete( entry );
97+
}
98+
}
99+
100+
// Extract the zip file content to the repo directory.
101+
console.WriteImportantMessage( $"Extracting '{file}' to repository." );
102+
103+
ZipFile.ExtractToDirectory( file, tempDirectory, true );
104+
105+
// Check if there are any changes.
106+
if ( !ToolInvocationHelper.InvokeTool(
107+
console,
108+
"git",
109+
"status --porcelain",
110+
tempDirectory,
111+
out _,
112+
out var statusOutput ) )
113+
{
114+
console.WriteError( "Failed to get git status." );
115+
116+
return SuccessCode.Fatal;
117+
}
118+
119+
var changes = statusOutput.Split( '\n', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries );
120+
121+
if ( changes.Length == 0 )
122+
{
123+
console.WriteSuccess( "No changes to commit. Repository is up to date." );
124+
125+
return SuccessCode.Success;
126+
}
127+
128+
console.WriteMessage( $"Found {changes.Length} changes to commit." );
129+
130+
// Stage all changes.
131+
if ( !ToolInvocationHelper.InvokeTool(
132+
console,
133+
"git",
134+
"add -A",
135+
tempDirectory ) )
136+
{
137+
console.WriteError( "Failed to stage changes." );
138+
139+
return SuccessCode.Fatal;
140+
}
141+
142+
// Commit the changes.
143+
// Escape double quotes in the commit message.
144+
var escapedMessage = commitMessage.Replace( "\"", "\\\"", StringComparison.Ordinal );
145+
146+
if ( !ToolInvocationHelper.InvokeTool(
147+
console,
148+
"git",
149+
$"commit -m \"{escapedMessage}\"",
150+
tempDirectory ) )
151+
{
152+
console.WriteError( "Failed to commit changes." );
153+
154+
return SuccessCode.Fatal;
155+
}
156+
157+
// Push to remote (skip in dry mode).
158+
if ( settings.Dry )
159+
{
160+
console.WriteImportantMessage( "Dry run: Skipping push to remote repository." );
161+
console.WriteSuccess( $"Dry run completed successfully for '{file}' to '{gitHubUrl}'." );
162+
}
163+
else
164+
{
165+
console.WriteImportantMessage( "Pushing changes to remote repository." );
166+
167+
if ( !ToolInvocationHelper.InvokeTool(
168+
console,
169+
"git",
170+
"push",
171+
tempDirectory ) )
172+
{
173+
console.WriteError( "Failed to push changes to remote repository." );
174+
175+
return SuccessCode.Fatal;
176+
}
177+
178+
console.WriteSuccess( $"Successfully published '{file}' to '{gitHubUrl}'." );
179+
}
180+
181+
return SuccessCode.Success;
182+
}
183+
catch ( Exception e )
184+
{
185+
console.WriteError( $"Error publishing to git repository: {e.Message}" );
186+
187+
return SuccessCode.Fatal;
188+
}
189+
finally
190+
{
191+
// Clean up the temporary directory.
192+
try
193+
{
194+
if ( Directory.Exists( tempDirectory ) )
195+
{
196+
// Reset read-only attributes on .git files before deleting.
197+
foreach ( var filePath in Directory.EnumerateFiles( tempDirectory, "*", SearchOption.AllDirectories ) )
198+
{
199+
File.SetAttributes( filePath, FileAttributes.Normal );
200+
}
201+
202+
Directory.Delete( tempDirectory, true );
203+
}
204+
}
205+
catch ( Exception e )
206+
{
207+
console.WriteWarning( $"Failed to clean up temporary directory '{tempDirectory}': {e.Message}" );
208+
}
209+
}
210+
}
211+
}

0 commit comments

Comments
 (0)