-
Notifications
You must be signed in to change notification settings - Fork 21
/
FormGitRun.cs
280 lines (250 loc) · 11.1 KB
/
FormGitRun.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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
using System;
using System.Drawing;
using System.Windows.Forms;
namespace GitForce
{
/// <summary>
/// Run a Git command using threading and a run window.
/// This form should be used for Git commands that take long time to complete, such are clone, push and pull.
/// </summary>
public partial class FormGitRun : Form
{
private Exec job;
private ExecResult result = new ExecResult();
/// <summary>
/// Class constructor that also pre-sets the command and argument to be run
/// </summary>
public FormGitRun(string cmd, string args)
{
InitializeComponent();
ClassWinGeometry.Restore(this);
checkAutoclose.Checked = Properties.Settings.Default.AutoCloseGitOnSuccess;
// Detect URL in this text box
textStdout.DetectUrls = true;
job = new Exec(cmd, args);
// Reuse the same font selected as fixed-pitch
textStdout.Font = Properties.Settings.Default.commitFont;
textStdout.Text += cmd + Environment.NewLine;
textStdout.Text += args + Environment.NewLine;
}
/// <summary>
/// Form is closing.
/// </summary>
private void FormGitRunFormClosing(object sender, FormClosingEventArgs e)
{
ClassWinGeometry.Save(this);
}
/// <summary>
/// Start running the preset command at the time form is first shown
/// </summary>
private void FormGitRunShown(object sender, EventArgs e)
{
UseWaitCursor = true;
// Start the job using our own output handlers
job.AsyncRun(PStdout, PStderr, PComplete);
}
/// <summary>
/// Returns the result structure from running a job
/// </summary>
public ExecResult GetResult()
{
return result;
}
/// <summary>
/// Callback that handles process printing to stdout
/// </summary>
private void PStdout(String message)
{
textStdout.AppendText(ClassUtils.ToPlainAscii(message) + Environment.NewLine);
// Keep the newly added text visible
textStdout.SelectionStart = textStdout.TextLength;
textStdout.ScrollToCaret();
}
/// <summary>
/// Callback that handles process printing to stderr.
/// Prints the stderr to a log window.
/// </summary>
private void PStderr(String message)
{
// Remove CSI [ or ESC [ + single character sequence
if (message.StartsWith("\u001b["))
message = message.Remove(0,3);
// This is a workaround for Linux Mono:
// On Windows, when we clone a remote repo, we receive each status line as a separate message
// On Linux, it is all clumped together without any newlines (or 0A), so we inject them
if (ClassUtils.IsMono())
{
// A bit of a hack since we simply hard-code recognized types of messages. Oh, well...
message = message.Replace("remote:", Environment.NewLine + "remote:");
message = message.Replace("Receiving", Environment.NewLine + "Receiving");
message = message.Replace("Resolving", Environment.NewLine + "Resolving");
}
textStdout.AppendText(ClassUtils.ToPlainAscii(message) + Environment.NewLine, Color.Red);
// Keep the newly added text visible
textStdout.SelectionStart = textStdout.TextLength;
textStdout.ScrollToCaret();
// This hack recognizes a common problem where the host RSA key was not added to the list
// of known hosts. Help the user by telling him that and (on Windows) offering to open the
// Manage Keys dialog.
if (message.Contains("key fingerprint is"))
{
// On Linux / Mono, we don't have a Manage SSH dialog
if (ClassUtils.IsMono())
{
MessageBox.Show(@"The remote server RSA key was not added to the list of known hosts.", @"Host Key error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
else
{
DialogResult response = MessageBox.Show(@"The remote server RSA key was not added to the list of known hosts." + Environment.NewLine +
@"Would you like to open the Manage SSH Keys dialog to add the host in the Remote Keys tab?", @"Host Key error",
MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation);
if (response == DialogResult.Yes)
{
FormSSH formSsh = new FormSSH();
formSsh.ShowDialog();
}
}
}
// This hack recognizes a common HTTPS authentication error message
if (message.Contains(@"fatal: Authentication failed for 'https:"))
{
MessageBox.Show(@"The remote server refused to authenticate you." + Environment.NewLine +
@"You need to set your full HTTPS credentials (user name and password) to access this repo.",
@"HTTPS Authentication error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
// Append the stderr stream message to a log window
App.PrintLogMessage("stderr: " + message, MessageType.Error);
}
/// <summary>
/// Callback that handles process completion event
/// </summary>
private void PComplete(ExecResult result)
{
UseWaitCursor = false;
this.result = result;
if (result.Success())
{
toolStripStatus.Text = "Git command completed successfully.";
textStdout.AppendText("Git command completed successfully.", Color.Green);
// On success, auto-close the dialog if the user's preference was checked
// This behavior can be skipped if the user holds down the Control key
if (Properties.Settings.Default.AutoCloseGitOnSuccess && Control.ModifierKeys != Keys.Control)
DialogResult = DialogResult.OK;
}
else
{
toolStripStatus.Text = "Git command failed!";
textStdout.AppendText("Git command failed!", Color.Red);
}
btCancel.Text = "Done";
StopProgress();
}
/// <summary>
/// When the user presses ESC key, close the dialog, but *only* if the git operation is completed.
/// We need to hook into the key chain and test the completion by checking the button text.
/// The text changes depending on the execution status.
/// </summary>
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Escape && btCancel.Text == "Done")
DialogResult = DialogResult.OK;
return base.ProcessCmdKey(ref msg, keyData);
}
/// <summary>
/// User presses a cancel button. This is a multi-function button
/// that starts as "Cancel"...
/// </summary>
private void BtCancelClick(object sender, EventArgs e)
{
StopProgress();
if (btCancel.Text == "Cancel")
{
textStdout.AppendText(Environment.NewLine + "Error: Git command interrupted!" + Environment.NewLine, Color.Purple);
toolStripStatus.Text = "Git command interrupted.";
job.Terminate();
btCancel.Text = "Close";
DialogResult = DialogResult.None;
}
else
{
if (btCancel.Text == "Done")
DialogResult = DialogResult.OK;
if (btCancel.Text == "Close")
DialogResult = DialogResult.Cancel;
}
}
/// <summary>
/// Call this function when the command completed, or is about to complete.
/// It signals to the user the end of command by enabling the text box and stopping the progress indicator.
/// </summary>
private void StopProgress()
{
textStdout.ReadOnly = false;
timerProgress.Enabled = false;
}
/// <summary>
/// The phase of the progress indicator (0..7)
/// </summary>
private int _progressPhase;
/// <summary>
/// Use timer to animate progress indicator.
/// </summary>
private void TimerProgressTick(object sender, EventArgs e)
{
toolStripStatus.Text = "Git command in progress... " + @"|/-\|/-\"[_progressPhase];
_progressPhase = (_progressPhase + 1) % 8;
}
/// <summary>
/// Called when the user clicks on an HTML link inside the output text.
/// </summary>
private void textLinkClicked(object sender, LinkClickedEventArgs e)
{
ClassUtils.OpenWebLink(e.LinkText);
}
/// <summary>
/// User clicked on the autoclose checkbox, changed the checked state
/// Update preferences with the new state
/// </summary>
private void CheckAutocloseCheckedChanged(object sender, EventArgs e)
{
Properties.Settings.Default.AutoCloseGitOnSuccess = checkAutoclose.Checked;
Properties.Settings.Default.Save();
}
/// <summary>
/// Context menu for the text box (which is read-only, so we can only select and copy text from it)
/// </summary>
private void TextStdoutMouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
ContextMenu contextMenu = new ContextMenu();
MenuItem menuItemSelectAll = new MenuItem("Select All");
menuItemSelectAll.Click += SelectAllAction;
contextMenu.MenuItems.Add(menuItemSelectAll);
MenuItem menuItemCopy = new MenuItem("Copy");
menuItemCopy.Click += CopyAction;
contextMenu.MenuItems.Add(menuItemCopy);
textStdout.ContextMenu = contextMenu;
}
}
/// <summary>
/// Context menu to select all text
/// </summary>
private void SelectAllAction(object sender, EventArgs e)
{
textStdout.SelectAll();
}
/// <summary>
/// Context menu to copy selected text onto the clipboard
/// </summary>
private void CopyAction(object sender, EventArgs e)
{
// Copy data in two formats: a simple text format and the more complex rich-text
// format that keeps the text color and font information
DataObject data = new DataObject();
data.SetData(DataFormats.Rtf, true, textStdout.SelectedRtf);
data.SetData(DataFormats.Text, true, textStdout.SelectedText);
Clipboard.SetDataObject(data, true);
}
}
}