-
-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathLanguageForgeSendReceiveActionHandler.cs
More file actions
215 lines (195 loc) · 10.8 KB
/
LanguageForgeSendReceiveActionHandler.cs
File metadata and controls
215 lines (195 loc) · 10.8 KB
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// Copyright (c) 2016 SIL International
// This software is licensed under the MIT License (http://opensource.org/licenses/MIT)
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Reflection;
using Chorus.sync;
using Chorus.VcsDrivers;
using LibFLExBridgeChorusPlugin.Infrastructure;
using LibTriboroughBridgeChorusPlugin;
using LibTriboroughBridgeChorusPlugin.Infrastructure;
using SIL.Code;
using SIL.Progress;
namespace LfMergeBridge
{
/// <summary>
/// Action handler used for Language Forge's Send/Receive.
/// </summary>
/// <remarks>
/// 1. This class does not (yet(?) support creating a new repository.
///
/// 2. Given that LF can reset the working set back to a previous long hash's commit,
/// then the initial commit here may create a second head of the current branch.
/// That should be fine here, since Chorus will merge those two heads, before sending it off to LD,
/// even if nothing new came in from LD.
/// </remarks>
[Export(typeof(IBridgeActionTypeHandler))]
internal sealed class LanguageForgeSendReceiveActionHandler : IBridgeActionTypeHandler
{
private const string FwData = "FixFwData";
private const string syncBase = "Sync";
private static string FindFwData()
{
// Try current working directory first
var cwd = Directory.GetCurrentDirectory();
var withExe = Path.Combine(cwd, FwData + ".exe");
if (File.Exists(withExe)) return withExe;
var withoutExe = Path.Combine(cwd, FwData);
if (File.Exists(withoutExe)) return withoutExe;
// Then try loction of LfMergeBridge.dll, as FixFwData is probably installed alongside as a sister file
var assembly = Assembly.GetExecutingAssembly();
var dir = Directory.GetParent(assembly.Location).FullName;
withExe = Path.Combine(dir, FwData + ".exe");
if (File.Exists(withExe)) return withExe;
withoutExe = Path.Combine(dir, FwData);
if (File.Exists(withoutExe)) return withoutExe;
return null;
}
/// <summary>
/// Do a Send/Receive with the matching Language Depot project for the given Language Forge project's repository.
/// </summary>
/// <remarks>This handler will *not* reset the workspace to another branch or long hash,
/// since doing so would prevent any new changes in the fwdata file from being processed.
///
/// If LF needs to sync with another commit (via its long hash),
/// LF *must* first use the action handler "LanguageForgeUpdateToLongHashActionHandler",
/// which will reset the workspace, and then LF can write new changes to the fwdata file,
/// and *then* call this action.
/// </remarks>
void IBridgeActionTypeHandler.StartWorking(IProgress progress, Dictionary<string, string> options, ref string somethingForClient)
{
// Make sure required parameters are in 'options'.
Require.That(options.ContainsKey(LfMergeBridgeUtilities.fullPathToProject), @"Missing required 'fullPathToProject' key in 'options'.");
Require.That(options.ContainsKey(LfMergeBridgeUtilities.fwdataFilename), @"Missing required 'fwdataFilename' key in 'options'.");
Require.That(options.ContainsKey(LfMergeBridgeUtilities.fdoDataModelVersion), @"Missing required 'fdoDataModelVersion' key in 'options'.");
Require.That(options.ContainsKey(LfMergeBridgeUtilities.languageDepotRepoName), @"Missing required 'languageDepotRepoName' key in 'options'.");
Require.That(options.ContainsKey(LfMergeBridgeUtilities.languageDepotRepoUri), @"Missing required 'languageDepotRepoUri' key in 'options'.");
// LfMergeBridgeUtilities.commitMessage is optional
var commitMessage = options.ContainsKey(LfMergeBridgeUtilities.commitMessage) ? options[LfMergeBridgeUtilities.commitMessage] : "sync";
var fwDataExePathname = FindFwData();
if (fwDataExePathname == null)
{
throw new InvalidOperationException(string.Format(@"Can't find {0} or {0}.exe", FwData));
}
// Syncing of a new repo (actually created here) is not supported.
var fullPathToProject = options[LfMergeBridgeUtilities.fullPathToProject];
if (!Directory.Exists(Path.Combine(fullPathToProject, ".hg")))
{
Directory.Delete(fullPathToProject, true);
LfMergeBridgeUtilities.AppendLineToSomethingForClient(ref somethingForClient, string.Format("{0} {1}: Cannot create a repository at this point in LF development. {2}", syncBase, LfMergeBridgeUtilities.failure, LfMergeBridgeUtilities.cloneDeleted));
return;
}
var projectFolderConfiguration = new ProjectFolderConfiguration(fullPathToProject);
FlexFolderSystem.ConfigureChorusProjectFolder(projectFolderConfiguration);
var synchronizer = Synchronizer.FromProjectConfiguration(projectFolderConfiguration, progress);
// Initial commit zero creation is not supported.
var hgRepository = synchronizer.Repository;
if (string.IsNullOrEmpty(hgRepository.Identifier))
{
Directory.Delete(fullPathToProject, true);
LfMergeBridgeUtilities.AppendLineToSomethingForClient(ref somethingForClient, string.Format("{0} {1}: Cannot do first commit. {2}.", syncBase, LfMergeBridgeUtilities.failure, LfMergeBridgeUtilities.cloneDeleted));
return;
}
var startingRevision = hgRepository.GetRevisionWorkingSetIsBasedOn();
IUpdateBranchHelperStrategy updateBranchHelperStrategy = new FlexUpdateBranchHelperStrategy();
var desiredBranchName = updateBranchHelperStrategy.GetBranchNameFromModelVersion(options[LfMergeBridgeUtilities.fdoDataModelVersion]);
var desiredModelVersion = updateBranchHelperStrategy.GetModelVersionFromBranchName(desiredBranchName);
// Do a pull first, to see if FLEx user has upgraded.
var uri = options[LfMergeBridgeUtilities.languageDepotRepoUri];
var repositoryAddress = RepositoryAddress.Create(options[LfMergeBridgeUtilities.languageDepotRepoName], uri, false);
var pulledChangesFromOthers = hgRepository.Pull(repositoryAddress, uri);
var highestHead = LfMergeBridgeUtilities.GetHighestRevision(hgRepository);
if (pulledChangesFromOthers)
{
// Check for a higher branch that came in.
if (updateBranchHelperStrategy.GetModelVersionFromBranchName(highestHead.Branch) > desiredModelVersion)
{
LfMergeBridgeUtilities.AppendLineToSomethingForClient(ref somethingForClient, string.Format("{0} {1}: pulled a higher model '{2}' than LF asked for '{3}': {4}.", syncBase, LfMergeBridgeUtilities.failure, highestHead.Branch, desiredBranchName, "Sync stopped before local commit"));
return;
}
}
var branch = startingRevision.Branch;
if (string.IsNullOrEmpty(branch))
{
// empty branch means default branch
branch = highestHead.Branch;
}
if (branch != desiredBranchName && highestHead.Branch != desiredBranchName)
{
if (desiredBranchName.Contains("."))
{
var idx = desiredBranchName.IndexOf('.');
var modelNumber = desiredBranchName.Substring(idx+1);
if (branch != modelNumber && highestHead.Branch != modelNumber)
{
// Not being the same could create a new branch, and LF doesn't allow that.
// It may be that LF ought to have first asked for a branch change.
LfMergeBridgeUtilities.AppendLineToSomethingForClient(ref somethingForClient, string.Format("{0} {1}: Cannot commit to current branch '{2}', because LF wants branch '{3}', and that could possibly create a new branch.", syncBase, LfMergeBridgeUtilities.failure, branch, desiredBranchName));
return;
}
}
else
{
// Not being the same could create a new branch, and LF doesn't allow that.
// It may be that LF ought to have first asked for a branch change.
LfMergeBridgeUtilities.AppendLineToSomethingForClient(ref somethingForClient, string.Format("{0} {1}: Cannot commit to current branch '{2}', because LF wants branch '{3}', and that could possibly create a new branch.", syncBase, LfMergeBridgeUtilities.failure, branch, desiredBranchName));
return;
}
}
// Set up adjunct.
var syncAdjunct = new FlexBridgeSynchronizerAdjunct(Path.Combine(fullPathToProject, options[LfMergeBridgeUtilities.fwdataFilename]), fwDataExePathname, true, false);
synchronizer.SynchronizerAdjunct = syncAdjunct;
// Set up sync options.
var assemblyName = Assembly.GetExecutingAssembly().GetName();
var syncOptions = new SyncOptions
{
DoPullFromOthers = false, // Already did the pull
DoMergeWithOthers = true,
DoSendToOthers = true,
CheckinDescription = string.Format("[{0}: {1}] {2}", assemblyName.Name, assemblyName.Version, commitMessage)
};
syncOptions.RepositorySourcesToTry.Clear(); // Get rid of any default ones, since LF only sends off to the internet (Language Depot).
// We use the generic creation code here to make testing easier. In the real world we will only create "HttpRepositoryPath".
syncOptions.RepositorySourcesToTry.Add(repositoryAddress);
var user = options.ContainsKey(LfMergeBridgeUtilities.user) ? options[LfMergeBridgeUtilities.user] : null;
if (user != null) hgRepository.SetUserNameInIni(user, progress);
progress.WriteVerbose("Syncing");
var syncResults = synchronizer.SyncNow(syncOptions);
if (!syncResults.Succeeded)
{
var message = string.Format("{0} {1}: {2}", syncBase, LfMergeBridgeUtilities.failure, syncResults.ErrorEncountered);
progress.WriteError(message);
LfMergeBridgeUtilities.AppendLineToSomethingForClient(ref somethingForClient, message);
return;
}
if (pulledChangesFromOthers)
{
// ENHANCE: A better fix would be in Chorus. Chorus should notice there was no new
// commit on the local branch, but that there is a higher head of the same branch
// than that of the current working set, and thus, it should tell the adjunct to
// do its simple update.
hgRepository.UpdateToBranchHead(desiredBranchName);
syncAdjunct.SimpleUpdate(progress, false);
}
// Fwdata file has been restored by this point.
var gotChangesText = (pulledChangesFromOthers || syncResults.DidGetChangesFromOthers) ? "Received changes from others" : "No changes from others";
// LF Merge needs to know if anything came from LD. Since new stuff did come in, then LF has to rebuild its FdoCache.
LfMergeBridgeUtilities.AppendLineToSomethingForClient(ref somethingForClient, string.Format("{0} {1}: {2}", syncBase, LfMergeBridgeUtilities.success, gotChangesText));
progress.WriteVerbose(gotChangesText);
// The fwdata file will have been updated by the FW adjunct by now, if anything came in the sync 'pull'.
// Write long SHA.
var revisionWorkingSetIsBasedOn = hgRepository.GetRevisionWorkingSetIsBasedOn();
// The long hash will be different, even if only a local commit was done.
LfMergeBridgeUtilities.WriteLongHash(progress, hgRepository, revisionWorkingSetIsBasedOn, ref somethingForClient);
}
/// <summary>
/// Get the type of action supported by the handler.
/// </summary>
ActionType IBridgeActionTypeHandler.SupportedActionType
{
get { return ActionType.LanguageForgeSendReceive; }
}
}
}