public static CommandStatus RunScript(IWin32Window owner, IGitModule module, string?scriptKey, IGitUICommands uiCommands, RevisionGridControl?revisionGrid) { try { return(RunScriptInternal(owner, module, scriptKey, uiCommands, revisionGrid)); } catch (ExternalOperationException ex) when(ex is not UserExternalOperationException) { ThreadHelper.AssertOnUIThread(); throw new UserExternalOperationException($"{TranslatedStrings.ScriptErrorFailedToExecute}: '{scriptKey}'", ex); } }
public async Task ReloadAsync() { var currentBranch = Module.GetSelectedBranch(); await this.SwitchToMainThreadAsync(); var token = CancelBackgroundTasks(); Enabled = false; try { treeMain.BeginUpdate(); _rootNodes.ForEach(t => t.IgnoreSelectionChangedEvent = true); var tasks = _rootNodes.Select(r => r.ReloadAsync(token)).ToArray(); // We ConfigureAwait(true) so that we're back on the UI thread when tasks are complete await Task.WhenAll(tasks).ConfigureAwait(true); ThreadHelper.AssertOnUIThread(); DisplayAheadBehindInformationForBranches(); } finally { if (!token.IsCancellationRequested) { Enabled = true; } // Need to end the update for the selected node to scroll into view below under certain conditions // (e.g. when the treeview gets larger, trying to make the selected node visible doesn't work). treeMain.EndUpdate(); if (!token.IsCancellationRequested) { var selectedNode = treeMain.AllNodes().FirstOrDefault(n => _rootNodes.Any(rn => $"{rn.TreeViewNode.FullPath}{treeMain.PathSeparator}{currentBranch}" == n.FullPath)); if (selectedNode != null) { SetSelectedNode(selectedNode); } _rootNodes.ForEach(t => t.IgnoreSelectionChangedEvent = false); } } }
private static CommandStatus RunScriptInternal(IWin32Window owner, IGitModule module, string?scriptKey, IGitUICommands uiCommands, RevisionGridControl?revisionGrid) { if (Strings.IsNullOrEmpty(scriptKey)) { return(false); } ScriptInfo?scriptInfo = ScriptManager.GetScript(scriptKey); if (scriptInfo is null) { ThreadHelper.AssertOnUIThread(); throw new UserExternalOperationException($"{TranslatedStrings.ScriptErrorCantFind}: '{scriptKey}'", new ExternalOperationException(command: null, arguments: null, module.WorkingDir, innerException: null)); } if (Strings.IsNullOrEmpty(scriptInfo.Command)) { return(false); } string?arguments = scriptInfo.Arguments; if (!Strings.IsNullOrEmpty(arguments) && revisionGrid is null) { string?optionDependingOnSelectedRevision = ScriptOptionsParser.Options.FirstOrDefault(option => ScriptOptionsParser.DependsOnSelectedRevision(option) && ScriptOptionsParser.Contains(arguments, option)); if (optionDependingOnSelectedRevision is not null) { ThreadHelper.AssertOnUIThread(); throw new UserExternalOperationException($"{TranslatedStrings.ScriptText}: '{scriptKey}'{Environment.NewLine}'{optionDependingOnSelectedRevision}' {TranslatedStrings.ScriptErrorOptionWithoutRevisionGridText}", new ExternalOperationException(scriptInfo.Command, arguments, module.WorkingDir, innerException: null)); } } if (scriptInfo.AskConfirmation && MessageBox.Show(owner, $"{TranslatedStrings.ScriptConfirmExecute}: '{scriptInfo.Name}'?", TranslatedStrings.ScriptText, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) { return(false); } string?originalCommand = scriptInfo.Command; (string?argument, bool abort) = ScriptOptionsParser.Parse(scriptInfo.Arguments, module, owner, revisionGrid); if (abort) { ThreadHelper.AssertOnUIThread(); throw new UserExternalOperationException($"{TranslatedStrings.ScriptText}: '{scriptKey}'{Environment.NewLine}{TranslatedStrings.ScriptErrorOptionWithoutRevisionText}", new ExternalOperationException(scriptInfo.Command, arguments, module.WorkingDir, innerException: null)); } Validates.NotNull(argument); string command = OverrideCommandWhenNecessary(originalCommand); command = ExpandCommandVariables(command, module); if (scriptInfo.IsPowerShell) { PowerShellHelper.RunPowerShell(command, argument, module.WorkingDir, scriptInfo.RunInBackground); // 'RunPowerShell' always runs the script detached (yet). // Hence currently, it does not make sense to set 'needsGridRefresh' to '!scriptInfo.RunInBackground'. return(new CommandStatus(executed: true, needsGridRefresh: false)); } if (command.StartsWith(PluginPrefix)) { command = command.Replace(PluginPrefix, string.Empty); lock (PluginRegistry.Plugins) { foreach (var plugin in PluginRegistry.Plugins) { if (string.Equals(plugin.Name, command, StringComparison.CurrentCultureIgnoreCase)) { var eventArgs = new GitUIEventArgs(owner, uiCommands); return(new CommandStatus(executed: true, needsGridRefresh: plugin.Execute(eventArgs))); } } } return(false); } if (command.StartsWith(NavigateToPrefix)) { if (revisionGrid is null) { return(false); } command = command.Replace(NavigateToPrefix, string.Empty); if (!Strings.IsNullOrEmpty(command)) { var revisionRef = new Executable(command, module.WorkingDir).GetOutputLines(argument).FirstOrDefault(); if (revisionRef is not null) { revisionGrid.GoToRef(revisionRef, true); } } return(new CommandStatus(executed: true, needsGridRefresh: false)); } if (!scriptInfo.RunInBackground) { bool success = FormProcess.ShowDialog(owner, command, argument, module.WorkingDir, null, true); if (!success) { return(false); } } else { if (originalCommand.Equals("{openurl}", StringComparison.CurrentCultureIgnoreCase)) { OsShellUtil.OpenUrlInDefaultBrowser(argument); } else { // It is totally valid to have a command without an argument, e.g.: // Command : myscript.cmd // Arguments: <blank> new Executable(command, module.WorkingDir).Start(argument ?? string.Empty); } } return(new CommandStatus(executed: true, needsGridRefresh: !scriptInfo.RunInBackground)); }
private bool DrawItem(Graphics wa, Graph.ILaneRow row) { ThreadHelper.AssertOnUIThread(); if (row == null || row.NodeLane == -1) { return(false); } // Clip to the area we're drawing in, but draw 1 pixel past so // that the top/bottom of the line segment's anti-aliasing isn't // visible in the final rendering. int top = wa.RenderingOrigin.Y + (_rowHeight / 2); var laneRect = new Rectangle(0, top, Width, _rowHeight); Region oldClip = wa.Clip; var newClip = new Region(laneRect); newClip.Intersect(oldClip); wa.Clip = newClip; wa.Clear(Color.Transparent); // Getting RevisionGraphDrawStyle results in call to AppSettings. This is not very cheap, cache. _revisionGraphDrawStyleCache = RevisionGraphDrawStyle; ////for (int r = 0; r < 2; r++) for (int lane = 0; lane < row.Count; lane++) { int mid = wa.RenderingOrigin.X + (int)((lane + 0.5) * _laneWidth); for (int item = 0; item < row.LaneInfoCount(lane); item++) { Graph.LaneInfo laneInfo = row[lane, item]; bool highLight = (_revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.DrawNonRelativesGray && laneInfo.Junctions.Any(j => j.IsRelative)) || (_revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.HighlightSelected && laneInfo.Junctions.Any(j => j.HighLight)) || (_revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.Normal); UpdateJunctionColors(laneInfo.Junctions); // Create the brush for drawing the line Brush lineBrush = null; Pen linePen = null; try { bool drawBorder = highLight && AppSettings.BranchBorders; // hide border for "non-relatives" if (_junctionColors.Count == 1 || !AppSettings.StripedBranchChange) { if (_junctionColors[0] != _nonRelativeColor) { lineBrush = new SolidBrush(_junctionColors[0]); } else if (_junctionColors.Count > 1 && _junctionColors[1] != _nonRelativeColor) { lineBrush = new SolidBrush(_junctionColors[1]); } else { drawBorder = false; lineBrush = new SolidBrush(_nonRelativeColor); } } else { Color lastRealColor = _junctionColors.LastOrDefault(c => c != _nonRelativeColor); if (lastRealColor.IsEmpty) { lineBrush = new SolidBrush(_nonRelativeColor); drawBorder = false; } else { lineBrush = new HatchBrush(HatchStyle.DarkDownwardDiagonal, _junctionColors[0], lastRealColor); } } // Precalculate line endpoints bool sameLane = laneInfo.ConnectLane == lane; int x0 = mid; int y0 = top - 1; int x1 = sameLane ? x0 : mid + ((laneInfo.ConnectLane - lane) * _laneWidth); int y1 = top + _rowHeight; Point p0 = new Point(x0, y0); Point p1 = new Point(x1, y1); // Precalculate curve control points when needed Point c0, c1; if (sameLane) { // We are drawing between two points in the same // lane, so there will be no curve c0 = c1 = default; } else { // Left shifting int is fast equivalent of dividing by two, // thus computing the average of y0 and y1. var yMid = (y0 + y1) >> 1; c0 = new Point(x0, yMid); c1 = new Point(x1, yMid); } for (int i = drawBorder ? 0 : 2; i < 3; i++) { Pen pen; switch (i) { case 0: pen = _whiteBorderPen; break; case 1: pen = _blackBorderPen; break; default: if (linePen == null) { linePen = new Pen(lineBrush, _laneLineWidth); } pen = linePen; break; } if (sameLane) { wa.DrawLine(pen, p0, p1); } else { wa.DrawBezier(pen, p0, c0, c1, p1); } } } finally { linePen?.Dispose(); lineBrush?.Dispose(); } } } // Reset the clip region wa.Clip = oldClip; // Draw node var nodeRect = new Rectangle( wa.RenderingOrigin.X + ((_laneWidth - _nodeDimension) / 2) + (row.NodeLane * _laneWidth), wa.RenderingOrigin.Y + ((_rowHeight - _nodeDimension) / 2), _nodeDimension, _nodeDimension); Brush nodeBrush; UpdateJunctionColors(row.Node.Ancestors); bool highlight = (_revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.DrawNonRelativesGray && row.Node.Ancestors.Any(j => j.IsRelative)) || (_revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.HighlightSelected && row.Node.Ancestors.Any(j => j.HighLight)) || (_revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.Normal); bool drawNodeBorder = AppSettings.BranchBorders && highlight; if (_junctionColors.Count == 1) { nodeBrush = new SolidBrush(highlight ? _junctionColors[0] : _nonRelativeColor); if (_junctionColors[0] == _nonRelativeColor) { drawNodeBorder = false; } } else { nodeBrush = new LinearGradientBrush( nodeRect, _junctionColors[0], _junctionColors[1], LinearGradientMode.Horizontal); if (_junctionColors.All(c => c == _nonRelativeColor)) { drawNodeBorder = false; } } if (row.Node.Data == null) { wa.FillEllipse(Brushes.White, nodeRect); using (var pen = new Pen(Color.Red, 2)) { wa.DrawEllipse(pen, nodeRect); } } else if (row.Node.IsActive) { wa.FillRectangle(nodeBrush, nodeRect); nodeRect.Inflate(1, 1); using (var pen = new Pen(Color.Black, 3)) { wa.DrawRectangle(pen, nodeRect); } } else if (row.Node.IsSpecial) { wa.FillRectangle(nodeBrush, nodeRect); if (drawNodeBorder) { wa.DrawRectangle(Pens.Black, nodeRect); } } else { wa.FillEllipse(nodeBrush, nodeRect); if (drawNodeBorder) { wa.DrawEllipse(Pens.Black, nodeRect); } } nodeBrush.Dispose(); return(true); void UpdateJunctionColors(IEnumerable <Junction> junction) { _junctionColors.Clear(); // Color of non-relative branches. _junctionColors.AddRange(junction.Select(GetJunctionColor)); if (_junctionColors.Count == 0) { _junctionColors.Add(Color.Black); } } }
private Color GetJunctionColor(Junction junction) { ThreadHelper.AssertOnUIThread(); // Draw non-relative branches gray if (!junction.IsRelative && _revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.DrawNonRelativesGray) { return(_nonRelativeColor); } // Draw non-highlighted branches gray if (!junction.HighLight && _revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.HighlightSelected) { return(_nonRelativeColor); } if (!AppSettings.MulticolorBranches) { return(AppSettings.GraphColor); } // See if this junciton's colour has already been calculated if (_colorByJunction.TryGetValue(junction, out var colorIndex)) { return(_possibleColors[colorIndex]); } // NOTE we reuse _adjacentColors to avoid allocating lists during UI painting. // This is safe as we are always on the UI thread here. _adjacentColors.Clear(); _adjacentColors.AddRange( from peer in GetPeers().SelectMany() where _colorByJunction.TryGetValue(peer, out colorIndex) select colorIndex); if (_adjacentColors.Count == 0) { // This is an end-point. We need to 'pick' a new color colorIndex = 0; } else { // This is a parent branch, calculate new color based on parent branch int start = _adjacentColors[0]; int i; for (i = 0; i < preferedColors.Length; i++) { colorIndex = (start + preferedColors[i]) % _possibleColors.Length; if (!_adjacentColors.Contains(colorIndex)) { break; } } if (i == preferedColors.Length) { colorIndex = _random.Next(preferedColors.Length); } } _colorByJunction[junction] = colorIndex; return(_possibleColors[colorIndex]); // Get adjacent (peer) junctions IEnumerable <IEnumerable <Junction> > GetPeers() { yield return(junction.Youngest.Ancestors); yield return(junction.Youngest.Descendants); yield return(junction.Oldest.Ancestors); yield return(junction.Oldest.Descendants); } }
private void Update() { ThreadHelper.AssertOnUIThread(); if (CurrentStatus != GitStatusMonitorState.Running) { return; } if (Environment.TickCount < _nextUpdateTime && (Environment.TickCount >= 0 || _nextUpdateTime <= 0)) { return; } // If the previous status call hasn't exited yet, we'll wait until it is // so we don't queue up a bunch of commands if (_commandIsRunning || // don't update status while repository is being modified by GitExt, // repository status will change after these actions. UICommandsSource.UICommands.RepoChangedNotifier.IsLocked || (GitVersion.Current.RaceConditionWhenGitStatusIsUpdatingIndex && Module.IsRunningGitProcess())) { _statusIsUpToDate = false; // schedule new update when command is finished return; } _workTreeWatcher.EnableRaisingEvents = true; _commandIsRunning = true; _statusIsUpToDate = true; _previousUpdateTime = Environment.TickCount; // Schedule update every 5 min, even if we don't know that anything changed CalculateNextUpdateTime(PeriodicUpdateInterval); // capture a consistent state in the main thread IGitModule module = Module; ThreadHelper.JoinableTaskFactory.RunAsync( async() => { try { try { await TaskScheduler.Default; var cmd = GitCommandHelpers.GetAllChangedFilesCmd(true, UntrackedFilesMode.Default, noLocks: true); var output = module.RunGitCmd(cmd); var changedFiles = GitCommandHelpers.GetStatusChangedFilesFromString(module, output); await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); if (!ModuleHasChanged()) { UpdatedStatusReceived(changedFiles); } } catch { try { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); // Avoid possible popups on every file changes CurrentStatus = GitStatusMonitorState.Stopped; } catch { // No action } throw; } } finally { _commandIsRunning = false; } }) .FileAndForget(); return; bool ModuleHasChanged() { return(module != Module); } void UpdatedStatusReceived(IEnumerable <GitItemStatus> changedFiles) { // Adjust the interval between updates. (This does not affect an update already scheduled). _currentUpdateInterval = Math.Max(MinUpdateInterval, 3 * (Environment.TickCount - _previousUpdateTime)); GitWorkingDirectoryStatusChanged?.Invoke(this, new GitWorkingDirectoryStatusEventArgs(changedFiles)); if (!_statusIsUpToDate) { // Command was requested when this command was running // Will always be scheduled as background update (even if requested interactively orignally) CalculateNextUpdateTime(); } } }
private void Update() { ThreadHelper.AssertOnUIThread(); if (CurrentStatus != GitStatusMonitorState.Running) { return; } if (Environment.TickCount < _nextUpdateTime) { return; } // If the previous status call hasn't exited yet, // schedule new update when command is finished if (_commandIsRunning) { ScheduleNextUpdateTime(0); return; } // don't update status while repository is being modified by GitExt, // repository status will change after these actions. if (UICommandsSource.UICommands.RepoChangedNotifier.IsLocked || (GitVersion.Current.RaceConditionWhenGitStatusIsUpdatingIndex && Module.IsRunningGitProcess())) { ScheduleNextUpdateTime(0); return; } _commandIsRunning = true; _nextIsInteractive = false; var commandStartTime = Environment.TickCount; _workTreeWatcher.EnableRaisingEvents = true; // Schedule update every 5 min, even if we don't know that anything changed ScheduleNextUpdateTime(PeriodicUpdateInterval); // capture a consistent state in the main thread IGitModule module = Module; ThreadHelper.JoinableTaskFactory.RunAsync( async() => { try { await TaskScheduler.Default; var cmd = GitCommandHelpers.GetAllChangedFilesCmd(true, UntrackedFilesMode.Default, noLocks: true); var output = module.RunGitCmd(cmd); var changedFiles = GitCommandHelpers.GetStatusChangedFilesFromString(module, output); await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); if (!ModuleHasChanged()) { UpdatedStatusReceived(changedFiles); } } catch { try { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); // Avoid possible popups on every file changes CurrentStatus = GitStatusMonitorState.Stopped; } catch { // No action } throw; } finally { _commandIsRunning = false; } }) .FileAndForget(); return; bool ModuleHasChanged() { return(module != Module); } void UpdatedStatusReceived(IEnumerable <GitItemStatus> changedFiles) { // Adjust the interval between updates, schedule new to recalculate _nextEarliestTime = commandStartTime + Math.Max(MinUpdateInterval, 3 * (Environment.TickCount - commandStartTime)); ScheduleNextUpdateTime(PeriodicUpdateInterval); GitWorkingDirectoryStatusChanged?.Invoke(this, new GitWorkingDirectoryStatusEventArgs(changedFiles)); } }