예제 #1
0
 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);
     }
 }
예제 #2
0
        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);
                }
            }
        }
예제 #3
0
        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));
        }
예제 #4
0
        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);
                }
            }
        }
예제 #5
0
        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);
            }
        }
예제 #6
0
        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();
                }
            }
        }
예제 #7
0
        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));
            }
        }