forked from jakubgarfield/Bonobo-Git-Server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
GitHandlerInvocationService.cs
201 lines (177 loc) · 8.18 KB
/
GitHandlerInvocationService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Diagnostics;
using Bonobo.Git.Server.Application.Hooks;
using LibGit2Sharp;
using Log = Serilog.Log;
namespace Bonobo.Git.Server.Git.GitService
{
/// <summary>
/// Invokes <see cref="IAfterGitPushHandler" /> according to transmitted Git protocol data.
/// </summary>
/// <remarks>
/// This service must be registered <strong>before</strong> <see cref="GitServiceExecutor" />, i.e. it has to wrap it
/// directly or indirectly.
/// </remarks>
public class GitHandlerInvocationService: IGitService
{
private readonly IGitService _next;
private readonly IAfterGitPushHandler _afterPushHandler;
private readonly IGitRepositoryLocator _repoLocator;
public GitHandlerInvocationService(IGitService next, IAfterGitPushHandler afterPushHandler, IGitRepositoryLocator repoLocator)
{
if (next == null) throw new ArgumentNullException(nameof(next));
if (afterPushHandler == null) throw new ArgumentNullException(nameof(afterPushHandler));
if (repoLocator == null) throw new ArgumentNullException(nameof(repoLocator));
_next = next;
_afterPushHandler = afterPushHandler;
_repoLocator = repoLocator;
}
public void ExecuteServiceByName(string correlationId, string repositoryName, string serviceName, ExecutionOptions options, Stream inStream, Stream outStream)
{
if (serviceName != "receive-pack")
{
_next.ExecuteServiceByName(correlationId, repositoryName, serviceName, options, inStream, outStream);
return;
}
var inspectStream = new ReceivePackInspectStream(inStream);
// this should actually run the git process and the push will be complete once this method returns
_next.ExecuteServiceByName(correlationId, repositoryName, serviceName, options, inspectStream, outStream);
// because the Git process has successfully finished, the request really shouldn't fail due to any exceptions thrown from here
try
{
Debug.WriteLine(
$"{nameof(GitHandlerInvocationService)} has found {inspectStream.PackObjectCount} objects in the receive-pack stream.");
DirectoryInfo repoDirectory = _repoLocator.GetRepositoryDirectoryPath(repositoryName);
Debug.Assert(repoDirectory.Exists);
using (Repository repository = new Repository(repoDirectory.FullName))
InvokeHandler(repositoryName, repository, inspectStream.PeekedCommands);
}
catch (Exception ex)
{
Log.Error($"Git after push handlers could not be invoked due to this exception:\n{ex}");
}
}
private void InvokeHandler(string repositoryName, Repository repository, IEnumerable<GitReceiveCommand> commands)
{
Debug.Assert(repositoryName != null);
Debug.Assert(repositoryName.Length > 0);
Debug.Assert(repository != null);
Debug.Assert(commands != null);
foreach (var command in commands)
{
try
{
if (command.RefType == GitRefType.Branch)
InvokeAccordingToBranchChange(repositoryName, repository, command);
else if (command.RefType == GitRefType.Tag)
InvokeAccordingToTagChange(repositoryName, repository, command);
}
catch (Exception ex)
{
Log.Error($"{nameof(IAfterGitPushHandler)} implementation has thrown an exception:\n{ex}");
}
}
}
private void InvokeAccordingToBranchChange(string repositoryName, Repository repository, GitReceiveCommand command)
{
Debug.Assert(repositoryName != null);
Debug.Assert(repository != null);
switch (command.CommandType) {
case GitProtocolCommand.Create:
{
var eventData = new GitBranchPushData
{
RepositoryName = repositoryName,
Repository = repository,
BranchName = command.RefName,
RefName = command.FullRefName,
ReferenceCommit = command.NewSha1,
AddedCommits = BranchCommits(repository, command.RefName).ToList()
};
_afterPushHandler.OnBranchCreated(HttpContext.Current, eventData);
break;
}
case GitProtocolCommand.Delete:
{
var evenData = new GitBranchPushData
{
RepositoryName = repositoryName,
Repository = repository,
BranchName = command.RefName,
RefName = command.FullRefName,
ReferenceCommit = command.OldSha1
};
_afterPushHandler.OnBranchDeleted(HttpContext.Current, evenData);
break;
}
case GitProtocolCommand.Modify: {
Branch branch = repository.Branches[command.RefName];
bool isFastForward = branch.Commits.Any(c => c.Sha == command.OldSha1);
IEnumerable<Commit> addedCommits = BranchCommits(repository, command.RefName)
.TakeWhile(c => c.Sha != command.OldSha1).ToList();
var eventData = new GitBranchPushData
{
RepositoryName = repositoryName,
Repository = repository,
BranchName = command.RefName,
RefName = command.FullRefName,
ReferenceCommit = command.NewSha1,
AddedCommits = addedCommits
};
_afterPushHandler.OnBranchModified(HttpContext.Current, eventData, isFastForward);
break;
}
}
}
private void InvokeAccordingToTagChange(string repositoryName, Repository repository, GitReceiveCommand command)
{
Debug.Assert(repositoryName != null);
Debug.Assert(repository != null);
switch (command.CommandType) {
case GitProtocolCommand.Create:
{
var eventData = new GitTagPushData
{
RepositoryName = repositoryName,
Repository = repository,
TagName = command.RefName,
RefName = command.FullRefName,
ReferenceCommitSha = command.NewSha1
};
_afterPushHandler.OnTagCreated(HttpContext.Current, eventData);
break;
}
case GitProtocolCommand.Delete:
{
var eventData = new GitTagPushData
{
RepositoryName = repositoryName,
Repository = repository,
TagName = command.RefName,
RefName = command.FullRefName,
ReferenceCommitSha = command.OldSha1
};
_afterPushHandler.OnTagDeleted(HttpContext.Current, eventData);
break;
}
}
}
private static IEnumerable<Commit> BranchCommits(Repository repository, string branchName)
{
var filter = new CommitFilter
{
IncludeReachableFrom = branchName,
SortBy = CommitSortStrategies.Topological,
FirstParentOnly = true
};
return repository.Commits
.QueryBy(filter)
.Intersect(repository.Branches[branchName].Commits);
}
}
}