tgstation-server 6.12.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
RepositoryManager.cs
Go to the documentation of this file.
1using System;
2using System.Threading;
3using System.Threading.Tasks;
4
5using LibGit2Sharp;
6
7using Microsoft.Extensions.Logging;
8
16
18{
21 {
23 public bool InUse => semaphore.CurrentCount == 0;
24
26 public bool CloneInProgress { get; private set; }
27
32
37
42
47
52
57
61 readonly ILogger<Repository> repositoryLogger;
62
66 readonly ILogger<RepositoryManager> logger;
67
72
76 readonly SemaphoreSlim semaphore;
77
97 ILogger<Repository> repositoryLogger,
98 ILogger<RepositoryManager> logger,
100 {
101 this.repositoryFactory = repositoryFactory ?? throw new ArgumentNullException(nameof(repositoryFactory));
102 this.commands = commands ?? throw new ArgumentNullException(nameof(commands));
103 this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
104 this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
105 this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler));
106 this.gitRemoteFeaturesFactory = gitRemoteFeaturesFactory ?? throw new ArgumentNullException(nameof(gitRemoteFeaturesFactory));
107 this.repositoryLogger = repositoryLogger ?? throw new ArgumentNullException(nameof(repositoryLogger));
108 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
109 this.generalConfiguration = generalConfiguration ?? throw new ArgumentNullException(nameof(generalConfiguration));
110 semaphore = new SemaphoreSlim(1);
111 }
112
114 public void Dispose()
115 {
116 logger.LogTrace("Disposing...");
117 semaphore.Dispose();
118 }
119
121 public async ValueTask<IRepository?> CloneRepository(
122 Uri url,
123 string? initialBranch,
124 string? username,
125 string? password,
126 JobProgressReporter progressReporter,
127 bool recurseSubmodules,
128 CancellationToken cancellationToken)
129 {
130 ArgumentNullException.ThrowIfNull(url);
131 lock (semaphore)
132 {
133 if (CloneInProgress)
134 throw new JobException(ErrorCode.RepoCloning);
135 CloneInProgress = true;
136 }
137
138 var repositoryPath = ioManager.ResolvePath();
139 logger.LogInformation("Begin clone {url} to {path} (Branch: {initialBranch})", url, repositoryPath, initialBranch);
140
141 try
142 {
143 using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
144 {
145 logger.LogTrace("Semaphore acquired for clone");
146 if (!await ioManager.DirectoryExists(repositoryPath, cancellationToken))
147 try
148 {
149 using var cloneProgressReporter = progressReporter.CreateSection(null, 0.75f);
150 using var checkoutProgressReporter = progressReporter.CreateSection(null, 0.25f);
151 var cloneOptions = new CloneOptions
152 {
153 RecurseSubmodules = recurseSubmodules,
154 OnCheckoutProgress = (path, completed, remaining) =>
155 {
156 if (checkoutProgressReporter == null)
157 return;
158
159 var percentage = (double)completed / remaining;
160 checkoutProgressReporter.ReportProgress(percentage);
161 },
162 BranchName = initialBranch,
163 };
164
165 cloneOptions.FetchOptions.Hydrate(
166 logger,
167 cloneProgressReporter,
169 cancellationToken);
170
172 url,
173 cloneOptions,
174 repositoryPath,
175 cancellationToken);
176 }
177 catch
178 {
179 try
180 {
181 logger.LogTrace("Deleting partially cloned repository...");
182
183 // DCT: Cancellation token is for job, operation must run regardless
184 await ioManager.DeleteDirectory(repositoryPath, CancellationToken.None);
185 }
186 catch (Exception innerException)
187 {
188 logger.LogError(innerException, "Error deleting partially cloned repository!");
189 }
190
191 throw;
192 }
193 else
194 {
195 logger.LogDebug("Repository exists, clone aborted!");
196 return null;
197 }
198 }
199 }
200 finally
201 {
202 logger.LogTrace("Semaphore released after clone");
203 CloneInProgress = false;
204 }
205
206 logger.LogInformation("Clone complete!");
207 return await LoadRepository(cancellationToken);
208 }
209
211 public async ValueTask<IRepository?> LoadRepository(CancellationToken cancellationToken)
212 {
213 logger.LogTrace("Begin LoadRepository...");
214 lock (semaphore)
215 if (CloneInProgress)
216 throw new JobException(ErrorCode.RepoCloning);
217
218 try
219 {
220 await semaphore.WaitAsync(cancellationToken);
221 try
222 {
223 logger.LogTrace("Semaphore acquired for load");
224 var libGit2Repo = await repositoryFactory.CreateFromPath(ioManager.ResolvePath(), cancellationToken);
225
226 return new Repository(
227 libGit2Repo,
228 commands,
229 ioManager,
237 () =>
238 {
239 logger.LogTrace("Releasing semaphore due to Repository disposal...");
240 semaphore.Release();
241 });
242 }
243 catch
244 {
245 logger.LogTrace("Releasing semaphore as load failed");
246 semaphore.Release();
247 throw;
248 }
249 }
250 catch (RepositoryNotFoundException e)
251 {
252 logger.LogTrace(e, "Repository not found!");
253 return null;
254 }
255 }
256
258 public async ValueTask DeleteRepository(CancellationToken cancellationToken)
259 {
260 logger.LogInformation("Deleting repository...");
261 try
262 {
263 using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
264 {
265 logger.LogTrace("Semaphore acquired, deleting Repository directory...");
266 await ioManager.DeleteDirectory(ioManager.ResolvePath(), cancellationToken);
267 }
268 }
269 finally
270 {
271 logger.LogTrace("Semaphore released after delete attempt");
272 }
273 }
274 }
275}
RepositoryManager(ILibGit2RepositoryFactory repositoryFactory, ILibGit2Commands commands, IIOManager ioManager, IEventConsumer eventConsumer, IPostWriteHandler postWriteHandler, IGitRemoteFeaturesFactory gitRemoteFeaturesFactory, ILogger< Repository > repositoryLogger, ILogger< RepositoryManager > logger, GeneralConfiguration generalConfiguration)
Initializes a new instance of the RepositoryManager class.
readonly ILogger< Repository > repositoryLogger
The ILogger created Repositorys.
async ValueTask DeleteRepository(CancellationToken cancellationToken)
Delete the current repository.A ValueTask representing the running operation.
readonly GeneralConfiguration generalConfiguration
The GeneralConfiguration for the RepositoryManager.
readonly ILibGit2RepositoryFactory repositoryFactory
The ILibGit2RepositoryFactory for the RepositoryManager.
readonly SemaphoreSlim semaphore
Used for controlling single access to the IRepository.
async ValueTask< IRepository?> LoadRepository(CancellationToken cancellationToken)
Attempt to load the IRepository from the default location.A ValueTask<TResult> resulting in the loade...
readonly ILibGit2Commands commands
The ILibGit2Commands for the RepositoryManager.
bool InUse
If something is holding a lock on the repository.
readonly IPostWriteHandler postWriteHandler
The IPostWriteHandler for the RepositoryManager.
readonly IIOManager ioManager
The IIOManager for the RepositoryManager.
async ValueTask< IRepository?> CloneRepository(Uri url, string? initialBranch, string? username, string? password, JobProgressReporter progressReporter, bool recurseSubmodules, CancellationToken cancellationToken)
Clone the repository at url .A ValueTask<TResult> resulting i the newly cloned IRepository,...
readonly ILogger< RepositoryManager > logger
The ILogger for the RepositoryManager.
readonly IEventConsumer eventConsumer
The IEventConsumer for the RepositoryManager.
bool CloneInProgress
If a CloneRepository(Uri, string, string, string, JobProgressReporter, bool, CancellationToken) opera...
readonly IGitRemoteFeaturesFactory gitRemoteFeaturesFactory
The IGitRemoteFeaturesFactory for the RepositoryManager.
Operation exceptions thrown from the context of a Models.Job.
JobProgressReporter CreateSection(string? newStageName, double percentage)
Create a subsection of the JobProgressReporter with its optional own stage name.
void ReportProgress(double? progress)
Report progress.
static async ValueTask< SemaphoreSlimContext > Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken, ILogger? logger=null)
Asyncronously locks a semaphore .
Consumes EventTypes and takes the appropriate actions.
CredentialsHandler GenerateCredentialsHandler(string? username, string? password)
Generate a CredentialsHandler from a given username and password .
For low level interactions with a LibGit2Sharp.IRepository.
Task Clone(Uri url, CloneOptions cloneOptions, string path, CancellationToken cancellationToken)
Clone a remote LibGit2Sharp.IRepository.
ValueTask< LibGit2Sharp.IRepository > CreateFromPath(string path, CancellationToken cancellationToken)
Load a LibGit2Sharp.IRepository from a given path .
Interface for using filesystems.
Definition IIOManager.cs:13
string ResolvePath()
Retrieve the full path of the current working directory.
Task DeleteDirectory(string path, CancellationToken cancellationToken)
Recursively delete a directory, removes and does not enter any symlinks encounterd.
Task< bool > DirectoryExists(string path, CancellationToken cancellationToken)
Check that the directory at path exists.
Handles changing file modes/permissions after writing.
ErrorCode
Types of Response.ErrorMessageResponses that the API may return.
Definition ErrorCode.cs:12