mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-05-20 03:34:59 +00:00
feature: git bisect
support
Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
parent
9eae1eeb81
commit
df5294bcb7
16 changed files with 397 additions and 7 deletions
|
@ -39,6 +39,7 @@
|
||||||
* Search commits
|
* Search commits
|
||||||
* GitFlow
|
* GitFlow
|
||||||
* Git LFS
|
* Git LFS
|
||||||
|
* Bisect
|
||||||
* Issue Link
|
* Issue Link
|
||||||
* Workspace
|
* Workspace
|
||||||
* Custom Action
|
* Custom Action
|
||||||
|
|
12
src/Commands/Bisect.cs
Normal file
12
src/Commands/Bisect.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
namespace SourceGit.Commands
|
||||||
|
{
|
||||||
|
public class Bisect : Command
|
||||||
|
{
|
||||||
|
public Bisect(string repo, string subcmd)
|
||||||
|
{
|
||||||
|
WorkingDirectory = repo;
|
||||||
|
Context = repo;
|
||||||
|
Args = $"bisect {subcmd}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
src/Models/Bisect.cs
Normal file
33
src/Models/Bisect.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SourceGit.Models
|
||||||
|
{
|
||||||
|
public enum BisectState
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
WaitingForRange,
|
||||||
|
Detecting,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BisectCommitFlag
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Good = 1,
|
||||||
|
Bad = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Bisect
|
||||||
|
{
|
||||||
|
public HashSet<string> Bads
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
} = [];
|
||||||
|
|
||||||
|
public HashSet<string> Goods
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
} = [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -168,6 +168,7 @@ namespace SourceGit.Models
|
||||||
_updateStashes = DateTime.Now.AddSeconds(.5).ToFileTime();
|
_updateStashes = DateTime.Now.AddSeconds(.5).ToFileTime();
|
||||||
}
|
}
|
||||||
else if (name.Equals("HEAD", StringComparison.Ordinal) ||
|
else if (name.Equals("HEAD", StringComparison.Ordinal) ||
|
||||||
|
name.Equals("BISECT_START", StringComparison.Ordinal) ||
|
||||||
name.StartsWith("refs/heads/", StringComparison.Ordinal) ||
|
name.StartsWith("refs/heads/", StringComparison.Ordinal) ||
|
||||||
name.StartsWith("refs/remotes/", StringComparison.Ordinal) ||
|
name.StartsWith("refs/remotes/", StringComparison.Ordinal) ||
|
||||||
(name.StartsWith("worktrees/", StringComparison.Ordinal) && name.EndsWith("/HEAD", StringComparison.Ordinal)))
|
(name.StartsWith("worktrees/", StringComparison.Ordinal) && name.EndsWith("/HEAD", StringComparison.Ordinal)))
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
<StreamGeometry x:Key="Icons.Action">M41 512c0-128 46-241 138-333C271 87 384 41 512 41s241 46 333 138c92 92 138 205 138 333s-46 241-138 333c-92 92-205 138-333 138s-241-46-333-138C87 753 41 640 41 512zm87 0c0 108 36 195 113 271s164 113 271 113c108 0 195-36 271-113s113-164 113-271-36-195-113-271c-77-77-164-113-271-113-108 0-195 36-271 113C164 317 128 404 128 512zm256 148V292l195 113L768 512l-195 113-195 113v-77zm148-113-61 36V440l61 36 61 36-61 36z</StreamGeometry>
|
<StreamGeometry x:Key="Icons.Action">M41 512c0-128 46-241 138-333C271 87 384 41 512 41s241 46 333 138c92 92 138 205 138 333s-46 241-138 333c-92 92-205 138-333 138s-241-46-333-138C87 753 41 640 41 512zm87 0c0 108 36 195 113 271s164 113 271 113c108 0 195-36 271-113s113-164 113-271-36-195-113-271c-77-77-164-113-271-113-108 0-195 36-271 113C164 317 128 404 128 512zm256 148V292l195 113L768 512l-195 113-195 113v-77zm148-113-61 36V440l61 36 61 36-61 36z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="Icons.AIAssist">M304 464a128 128 0 01128-128c71 0 128 57 128 128v224a32 32 0 01-64 0V592h-128v95a32 32 0 01-64 0v-224zm64 1v64h128v-64a64 64 0 00-64-64c-35 0-64 29-64 64zM688 337c18 0 32 14 32 32v319a32 32 0 01-32 32c-18 0-32-14-32-32v-319a32 32 0 0132-32zM84 911l60-143A446 446 0 0164 512C64 265 265 64 512 64s448 201 448 448-201 448-448 448c-54 0-105-9-153-27l-242 22a32 32 0 01-32-44zm133-150-53 126 203-18 13 5c41 15 85 23 131 23 212 0 384-172 384-384S724 128 512 128 128 300 128 512c0 82 26 157 69 220l20 29z</StreamGeometry>
|
<StreamGeometry x:Key="Icons.AIAssist">M304 464a128 128 0 01128-128c71 0 128 57 128 128v224a32 32 0 01-64 0V592h-128v95a32 32 0 01-64 0v-224zm64 1v64h128v-64a64 64 0 00-64-64c-35 0-64 29-64 64zM688 337c18 0 32 14 32 32v319a32 32 0 01-32 32c-18 0-32-14-32-32v-319a32 32 0 0132-32zM84 911l60-143A446 446 0 0164 512C64 265 265 64 512 64s448 201 448 448-201 448-448 448c-54 0-105-9-153-27l-242 22a32 32 0 01-32-44zm133-150-53 126 203-18 13 5c41 15 85 23 131 23 212 0 384-172 384-384S724 128 512 128 128 300 128 512c0 82 26 157 69 220l20 29z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="Icons.Archive">M296 392h64v64h-64zM296 582v160h128V582h-64v-62h-64v62zm80 48v64h-32v-64h32zM360 328h64v64h-64zM296 264h64v64h-64zM360 456h64v64h-64zM360 200h64v64h-64zM855 289 639 73c-6-6-14-9-23-9H192c-18 0-32 14-32 32v832c0 18 14 32 32 32h640c18 0 32-14 32-32V311c0-9-3-17-9-23zM790 326H602V138L790 326zm2 562H232V136h64v64h64v-64h174v216c0 23 19 42 42 42h216v494z</StreamGeometry>
|
<StreamGeometry x:Key="Icons.Archive">M296 392h64v64h-64zM296 582v160h128V582h-64v-62h-64v62zm80 48v64h-32v-64h32zM360 328h64v64h-64zM296 264h64v64h-64zM360 456h64v64h-64zM360 200h64v64h-64zM855 289 639 73c-6-6-14-9-23-9H192c-18 0-32 14-32 32v832c0 18 14 32 32 32h640c18 0 32-14 32-32V311c0-9-3-17-9-23zM790 326H602V138L790 326zm2 562H232V136h64v64h64v-64h174v216c0 23 19 42 42 42h216v494z</StreamGeometry>
|
||||||
|
<StreamGeometry x:Key="Icons.Bad">M851 755q0 23-16 39l-78 78q-16 16-39 16t-39-16l-168-168-168 168q-16 16-39 16t-39-16l-78-78q-16-16-16-39t16-39l168-168-168-168q-16-16-16-39t16-39l78-78q16-16 39-16t39 16l168 168 168-168q16-16 39-16t39 16l78 78q16 16 16 39t-16 39l-168 168 168 168q16 16 16 39z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="Icons.Binary">M71 1024V0h661L953 219V1024H71zm808-731-220-219H145V951h735V293zM439 512h-220V219h220V512zm-74-219H292v146h74v-146zm0 512h74v73h-220v-73H292v-146H218V585h147v219zm294-366h74V512H512v-73h74v-146H512V219h147v219zm74 439H512V585h220v293zm-74-219h-74v146h74v-146z</StreamGeometry>
|
<StreamGeometry x:Key="Icons.Binary">M71 1024V0h661L953 219V1024H71zm808-731-220-219H145V951h735V293zM439 512h-220V219h220V512zm-74-219H292v146h74v-146zm0 512h74v73h-220v-73H292v-146H218V585h147v219zm294-366h74V512H512v-73h74v-146H512V219h147v219zm74 439H512V585h220v293zm-74-219h-74v146h74v-146z</StreamGeometry>
|
||||||
|
<StreamGeometry x:Key="Icons.Bisect">M128 384a43 43 0 0043-43V213a43 43 0 0143-43h128a43 43 0 000-85H213a128 128 0 00-128 128v128a43 43 0 0043 43zm213 469H213a43 43 0 01-43-43v-128a43 43 0 00-85 0v128a128 128 0 00128 128h128a43 43 0 000-85zm384-299a43 43 0 000-85h-49A171 171 0 00555 347V299a43 43 0 00-85 0v49A171 171 0 00347 469H299a43 43 0 000 85h49A171 171 0 00469 677V725a43 43 0 0085 0v-49A171 171 0 00677 555zm-213 43a85 85 0 1185-85 85 85 0 01-85 85zm384 43a43 43 0 00-43 43v128a43 43 0 01-43 43h-128a43 43 0 000 85h128a128 128 0 00128-128v-128a43 43 0 00-43-43zM811 85h-128a43 43 0 000 85h128a43 43 0 0143 43v128a43 43 0 0085 0V213a128 128 0 00-128-128z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="Icons.Blame">M128 256h192a64 64 0 110 128H128a64 64 0 110-128zm576 192h192a64 64 0 010 128h-192a64 64 0 010-128zm-576 192h192a64 64 0 010 128H128a64 64 0 010-128zm576 0h192a64 64 0 010 128h-192a64 64 0 010-128zm0-384h192a64 64 0 010 128h-192a64 64 0 010-128zM128 448h192a64 64 0 110 128H128a64 64 0 110-128zm384-320a64 64 0 0164 64v640a64 64 0 01-128 0V192a64 64 0 0164-64z</StreamGeometry>
|
<StreamGeometry x:Key="Icons.Blame">M128 256h192a64 64 0 110 128H128a64 64 0 110-128zm576 192h192a64 64 0 010 128h-192a64 64 0 010-128zm-576 192h192a64 64 0 010 128H128a64 64 0 010-128zm576 0h192a64 64 0 010 128h-192a64 64 0 010-128zm0-384h192a64 64 0 010 128h-192a64 64 0 010-128zM128 448h192a64 64 0 110 128H128a64 64 0 110-128zm384-320a64 64 0 0164 64v640a64 64 0 01-128 0V192a64 64 0 0164-64z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="Icons.Bookmark">M832 64H192c-18 0-32 14-32 32v832c0 18 14 32 32 32h640c18 0 32-14 32-32V96c0-18-14-32-32-32zM736 596 624 502 506 596V131h230v318z</StreamGeometry>
|
<StreamGeometry x:Key="Icons.Bookmark">M832 64H192c-18 0-32 14-32 32v832c0 18 14 32 32 32h640c18 0 32-14 32-32V96c0-18-14-32-32-32zM736 596 624 502 506 596V131h230v318z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="Icons.Bottom">M509 546 780 275 871 366 509 728 147 366 238 275zM509 728h-362v128h724v-128z</StreamGeometry>
|
<StreamGeometry x:Key="Icons.Bottom">M509 546 780 275 871 366 509 728 147 366 238 275zM509 728h-362v128h724v-128z</StreamGeometry>
|
||||||
|
|
|
@ -36,6 +36,13 @@
|
||||||
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">NO FILES ASSUMED AS UNCHANGED</x:String>
|
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">NO FILES ASSUMED AS UNCHANGED</x:String>
|
||||||
<x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">REMOVE</x:String>
|
<x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">REMOVE</x:String>
|
||||||
<x:String x:Key="Text.BinaryNotSupported" xml:space="preserve">BINARY FILE NOT SUPPORTED!!!</x:String>
|
<x:String x:Key="Text.BinaryNotSupported" xml:space="preserve">BINARY FILE NOT SUPPORTED!!!</x:String>
|
||||||
|
<x:String x:Key="Text.Bisect">Bisect</x:String>
|
||||||
|
<x:String x:Key="Text.Bisect.Abort">Abort</x:String>
|
||||||
|
<x:String x:Key="Text.Bisect.Bad">Bad</x:String>
|
||||||
|
<x:String x:Key="Text.Bisect.Detecting">Bisecting. Is current HEAD good or bad?</x:String>
|
||||||
|
<x:String x:Key="Text.Bisect.Good">Good</x:String>
|
||||||
|
<x:String x:Key="Text.Bisect.Skip">Skip</x:String>
|
||||||
|
<x:String x:Key="Text.Bisect.WaitingForRange">Bisecting. Mark current commit as good or bad and checkout another one.</x:String>
|
||||||
<x:String x:Key="Text.Blame" xml:space="preserve">Blame</x:String>
|
<x:String x:Key="Text.Blame" xml:space="preserve">Blame</x:String>
|
||||||
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">BLAME ON THIS FILE IS NOT SUPPORTED!!!</x:String>
|
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">BLAME ON THIS FILE IS NOT SUPPORTED!!!</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">Checkout ${0}$...</x:String>
|
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">Checkout ${0}$...</x:String>
|
||||||
|
|
|
@ -40,6 +40,13 @@
|
||||||
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">没有不跟踪更改的文件</x:String>
|
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">没有不跟踪更改的文件</x:String>
|
||||||
<x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">移除</x:String>
|
<x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">移除</x:String>
|
||||||
<x:String x:Key="Text.BinaryNotSupported" xml:space="preserve">二进制文件不支持该操作!!!</x:String>
|
<x:String x:Key="Text.BinaryNotSupported" xml:space="preserve">二进制文件不支持该操作!!!</x:String>
|
||||||
|
<x:String x:Key="Text.Bisect">二分定位(bisect)</x:String>
|
||||||
|
<x:String x:Key="Text.Bisect.Abort">终止</x:String>
|
||||||
|
<x:String x:Key="Text.Bisect.Bad">标记错误</x:String>
|
||||||
|
<x:String x:Key="Text.Bisect.Detecting">二分定位进行中。当前提交是 '正确' 还是 '错误'?</x:String>
|
||||||
|
<x:String x:Key="Text.Bisect.Good">标记正确</x:String>
|
||||||
|
<x:String x:Key="Text.Bisect.Skip">该提交无法判定</x:String>
|
||||||
|
<x:String x:Key="Text.Bisect.WaitingForRange">二分定位进行中。请标记当前的提交是 '正确' 还是 '错误',然后检出另一个提交。</x:String>
|
||||||
<x:String x:Key="Text.Blame" xml:space="preserve">逐行追溯(blame)</x:String>
|
<x:String x:Key="Text.Blame" xml:space="preserve">逐行追溯(blame)</x:String>
|
||||||
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">选中文件不支持该操作!!!</x:String>
|
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">选中文件不支持该操作!!!</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">检出(checkout) ${0}$...</x:String>
|
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">检出(checkout) ${0}$...</x:String>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@ -62,6 +63,12 @@ namespace SourceGit.ViewModels
|
||||||
set => SetProperty(ref _detailContext, value);
|
set => SetProperty(ref _detailContext, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Models.Bisect Bisect
|
||||||
|
{
|
||||||
|
get => _bisect;
|
||||||
|
private set => SetProperty(ref _bisect, value);
|
||||||
|
}
|
||||||
|
|
||||||
public GridLength LeftArea
|
public GridLength LeftArea
|
||||||
{
|
{
|
||||||
get => _leftArea;
|
get => _leftArea;
|
||||||
|
@ -111,6 +118,37 @@ namespace SourceGit.ViewModels
|
||||||
_detailContext = null;
|
_detailContext = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Models.BisectState UpdateBisectInfo()
|
||||||
|
{
|
||||||
|
var test = Path.Combine(_repo.GitDir, "BISECT_START");
|
||||||
|
if (!File.Exists(test))
|
||||||
|
{
|
||||||
|
Bisect = null;
|
||||||
|
return Models.BisectState.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
var info = new Models.Bisect();
|
||||||
|
var dir = Path.Combine(_repo.GitDir, "refs", "bisect");
|
||||||
|
if (Directory.Exists(dir))
|
||||||
|
{
|
||||||
|
var files = new DirectoryInfo(dir).GetFiles();
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
if (file.Name.StartsWith("bad"))
|
||||||
|
info.Bads.Add(File.ReadAllText(file.FullName).Trim());
|
||||||
|
else if (file.Name.StartsWith("good"))
|
||||||
|
info.Goods.Add(File.ReadAllText(file.FullName).Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bisect = info;
|
||||||
|
|
||||||
|
if (info.Bads.Count == 0 || info.Goods.Count == 0)
|
||||||
|
return Models.BisectState.WaitingForRange;
|
||||||
|
else
|
||||||
|
return Models.BisectState.Detecting;
|
||||||
|
}
|
||||||
|
|
||||||
public void NavigateTo(string commitSHA)
|
public void NavigateTo(string commitSHA)
|
||||||
{
|
{
|
||||||
var commit = _commits.Find(x => x.SHA.StartsWith(commitSHA, StringComparison.Ordinal));
|
var commit = _commits.Find(x => x.SHA.StartsWith(commitSHA, StringComparison.Ordinal));
|
||||||
|
@ -1209,7 +1247,7 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
builder.Append(".patch");
|
builder.Append(".patch");
|
||||||
|
|
||||||
return System.IO.Path.Combine(dir, builder.ToString());
|
return Path.Combine(dir, builder.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Repository _repo = null;
|
private Repository _repo = null;
|
||||||
|
@ -1220,6 +1258,8 @@ namespace SourceGit.ViewModels
|
||||||
private long _navigationId = 0;
|
private long _navigationId = 0;
|
||||||
private object _detailContext = null;
|
private object _detailContext = null;
|
||||||
|
|
||||||
|
private Models.Bisect _bisect = null;
|
||||||
|
|
||||||
private GridLength _leftArea = new GridLength(1, GridUnitType.Star);
|
private GridLength _leftArea = new GridLength(1, GridUnitType.Star);
|
||||||
private GridLength _rightArea = new GridLength(1, GridUnitType.Star);
|
private GridLength _rightArea = new GridLength(1, GridUnitType.Star);
|
||||||
private GridLength _topArea = new GridLength(1, GridUnitType.Star);
|
private GridLength _topArea = new GridLength(1, GridUnitType.Star);
|
||||||
|
|
|
@ -398,6 +398,18 @@ namespace SourceGit.ViewModels
|
||||||
get => _workingCopy?.InProgressContext;
|
get => _workingCopy?.InProgressContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Models.BisectState BisectState
|
||||||
|
{
|
||||||
|
get => _bisectState;
|
||||||
|
private set => SetProperty(ref _bisectState, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsBisectCommandRunning
|
||||||
|
{
|
||||||
|
get => _isBisectCommandRunning;
|
||||||
|
private set => SetProperty(ref _isBisectCommandRunning, value);
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsAutoFetching
|
public bool IsAutoFetching
|
||||||
{
|
{
|
||||||
get => _isAutoFetching;
|
get => _isAutoFetching;
|
||||||
|
@ -939,6 +951,31 @@ namespace SourceGit.ViewModels
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Bisect(string subcmd)
|
||||||
|
{
|
||||||
|
IsBisectCommandRunning = true;
|
||||||
|
SetWatcherEnabled(false);
|
||||||
|
|
||||||
|
var log = CreateLog($"Bisect({subcmd})");
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
var succ = new Commands.Bisect(_fullpath, subcmd).Use(log).Exec();
|
||||||
|
log.Complete();
|
||||||
|
|
||||||
|
Dispatcher.UIThread.Invoke(() =>
|
||||||
|
{
|
||||||
|
if (!succ)
|
||||||
|
App.RaiseException(_fullpath, log.Content);
|
||||||
|
else if (log.Content.Contains("is the first bad commit"))
|
||||||
|
App.SendNotification(_fullpath, log.Content);
|
||||||
|
|
||||||
|
MarkBranchesDirtyManually();
|
||||||
|
SetWatcherEnabled(true);
|
||||||
|
IsBisectCommandRunning = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void RefreshBranches()
|
public void RefreshBranches()
|
||||||
{
|
{
|
||||||
var branches = new Commands.QueryBranches(_fullpath).Result();
|
var branches = new Commands.QueryBranches(_fullpath).Result();
|
||||||
|
@ -1023,6 +1060,8 @@ namespace SourceGit.ViewModels
|
||||||
_histories.Commits = commits;
|
_histories.Commits = commits;
|
||||||
_histories.Graph = graph;
|
_histories.Graph = graph;
|
||||||
|
|
||||||
|
BisectState = _histories.UpdateBisectInfo();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_navigateToBranchDelayed))
|
if (!string.IsNullOrEmpty(_navigateToBranchDelayed))
|
||||||
{
|
{
|
||||||
var branch = _branches.Find(x => x.FullName == _navigateToBranchDelayed);
|
var branch = _branches.Find(x => x.FullName == _navigateToBranchDelayed);
|
||||||
|
@ -2627,6 +2666,9 @@ namespace SourceGit.ViewModels
|
||||||
private Timer _autoFetchTimer = null;
|
private Timer _autoFetchTimer = null;
|
||||||
private DateTime _lastFetchTime = DateTime.MinValue;
|
private DateTime _lastFetchTime = DateTime.MinValue;
|
||||||
|
|
||||||
|
private Models.BisectState _bisectState = Models.BisectState.None;
|
||||||
|
private bool _isBisectCommandRunning = false;
|
||||||
|
|
||||||
private string _navigateToBranchDelayed = string.Empty;
|
private string _navigateToBranchDelayed = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
134
src/Views/BisectStateIndicator.cs
Normal file
134
src/Views/BisectStateIndicator.cs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media;
|
||||||
|
|
||||||
|
namespace SourceGit.Views
|
||||||
|
{
|
||||||
|
public class BisectStateIndicator : Control
|
||||||
|
{
|
||||||
|
public static readonly StyledProperty<IBrush> BackgroundProperty =
|
||||||
|
AvaloniaProperty.Register<BisectStateIndicator, IBrush>(nameof(Background), Brushes.Transparent);
|
||||||
|
|
||||||
|
public IBrush Background
|
||||||
|
{
|
||||||
|
get => GetValue(BackgroundProperty);
|
||||||
|
set => SetValue(BackgroundProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush> ForegroundProperty =
|
||||||
|
AvaloniaProperty.Register<BisectStateIndicator, IBrush>(nameof(Foreground), Brushes.White);
|
||||||
|
|
||||||
|
public IBrush Foreground
|
||||||
|
{
|
||||||
|
get => GetValue(ForegroundProperty);
|
||||||
|
set => SetValue(ForegroundProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<Models.Bisect> BisectProperty =
|
||||||
|
AvaloniaProperty.Register<BisectStateIndicator, Models.Bisect>(nameof(Bisect));
|
||||||
|
|
||||||
|
public Models.Bisect Bisect
|
||||||
|
{
|
||||||
|
get => GetValue(BisectProperty);
|
||||||
|
set => SetValue(BisectProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BisectStateIndicator()
|
||||||
|
{
|
||||||
|
AffectsMeasure<BisectStateIndicator>(BisectProperty);
|
||||||
|
AffectsRender<BisectStateIndicator>(BackgroundProperty, ForegroundProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Render(DrawingContext context)
|
||||||
|
{
|
||||||
|
if (_flags == Models.BisectCommitFlag.None)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_prefix == null)
|
||||||
|
{
|
||||||
|
_prefix = LoadIcon("Icons.Bisect");
|
||||||
|
_good = LoadIcon("Icons.Check");
|
||||||
|
_bad = LoadIcon("Icons.Bad");
|
||||||
|
}
|
||||||
|
|
||||||
|
var x = 0.0;
|
||||||
|
|
||||||
|
if (_flags.HasFlag(Models.BisectCommitFlag.Good))
|
||||||
|
{
|
||||||
|
RenderImpl(context, Brushes.Green, _good, x);
|
||||||
|
x += 36;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_flags.HasFlag(Models.BisectCommitFlag.Bad))
|
||||||
|
RenderImpl(context, Brushes.Red, _bad, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Size MeasureOverride(Size availableSize)
|
||||||
|
{
|
||||||
|
var desiredFlags = Models.BisectCommitFlag.None;
|
||||||
|
var desiredWidth = 0.0;
|
||||||
|
if (Bisect is { } bisect && DataContext is Models.Commit commit)
|
||||||
|
{
|
||||||
|
var sha = commit.SHA;
|
||||||
|
if (bisect.Goods.Contains(sha))
|
||||||
|
{
|
||||||
|
desiredFlags |= Models.BisectCommitFlag.Good;
|
||||||
|
desiredWidth = 36;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bisect.Bads.Contains(sha))
|
||||||
|
{
|
||||||
|
desiredFlags |= Models.BisectCommitFlag.Bad;
|
||||||
|
desiredWidth += 36;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (desiredFlags != _flags)
|
||||||
|
{
|
||||||
|
_flags = desiredFlags;
|
||||||
|
InvalidateVisual();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Size(desiredWidth, desiredWidth > 0 ? 16 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Geometry LoadIcon(string key)
|
||||||
|
{
|
||||||
|
var geo = this.FindResource(key) as StreamGeometry;
|
||||||
|
var drawGeo = geo!.Clone();
|
||||||
|
var iconBounds = drawGeo.Bounds;
|
||||||
|
var translation = Matrix.CreateTranslation(-(Vector)iconBounds.Position);
|
||||||
|
var scale = Math.Min(10.0 / iconBounds.Width, 10.0 / iconBounds.Height);
|
||||||
|
var transform = translation * Matrix.CreateScale(scale, scale);
|
||||||
|
if (drawGeo.Transform == null || drawGeo.Transform.Value == Matrix.Identity)
|
||||||
|
drawGeo.Transform = new MatrixTransform(transform);
|
||||||
|
else
|
||||||
|
drawGeo.Transform = new MatrixTransform(drawGeo.Transform.Value * transform);
|
||||||
|
|
||||||
|
return drawGeo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderImpl(DrawingContext context, IBrush brush, Geometry icon, double x)
|
||||||
|
{
|
||||||
|
var entireRect = new RoundedRect(new Rect(x, 0, 32, 16), new CornerRadius(2));
|
||||||
|
var stateRect = new RoundedRect(new Rect(x + 16, 0, 16, 16), new CornerRadius(0, 2, 2, 0));
|
||||||
|
context.DrawRectangle(Background, new Pen(brush), entireRect);
|
||||||
|
using (context.PushOpacity(.2))
|
||||||
|
context.DrawRectangle(brush, null, stateRect);
|
||||||
|
context.DrawLine(new Pen(brush), new Point(x + 16, 0), new Point(x + 16, 16));
|
||||||
|
|
||||||
|
using (context.PushTransform(Matrix.CreateTranslation(x + 3, 3)))
|
||||||
|
context.DrawGeometry(Foreground, null, _prefix);
|
||||||
|
|
||||||
|
using (context.PushTransform(Matrix.CreateTranslation(x + 19, 3)))
|
||||||
|
context.DrawGeometry(Foreground, null, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Geometry _prefix = null;
|
||||||
|
private Geometry _good = null;
|
||||||
|
private Geometry _bad = null;
|
||||||
|
private Models.BisectCommitFlag _flags = Models.BisectCommitFlag.None;
|
||||||
|
}
|
||||||
|
}
|
|
@ -131,14 +131,20 @@
|
||||||
ClipToBounds="True"
|
ClipToBounds="True"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
ToolTip.Tip="{Binding Subject}">
|
ToolTip.Tip="{Binding Subject}">
|
||||||
<Grid ColumnDefinitions="Auto,Auto,*" Margin="2,0,4,0" ClipToBounds="True">
|
<Grid ColumnDefinitions="Auto,Auto,Auto,*" Margin="2,0,4,0" ClipToBounds="True">
|
||||||
<v:CommitStatusIndicator Grid.Column="0"
|
<v:CommitStatusIndicator Grid.Column="0"
|
||||||
CurrentBranch="{Binding $parent[v:Histories].CurrentBranch}"
|
CurrentBranch="{Binding $parent[v:Histories].CurrentBranch}"
|
||||||
AheadBrush="{DynamicResource Brush.Accent}"
|
AheadBrush="{DynamicResource Brush.Accent}"
|
||||||
BehindBrush="{DynamicResource Brush.FG1}"
|
BehindBrush="{DynamicResource Brush.FG1}"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
|
|
||||||
|
<v:BisectStateIndicator Grid.Column="1"
|
||||||
|
Background="{DynamicResource Brush.Contents}"
|
||||||
|
Foreground="{DynamicResource Brush.FG1}"
|
||||||
|
Bisect="{Binding $parent[v:Histories].Bisect}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
|
||||||
<v:CommitRefsPresenter Grid.Column="1"
|
<v:CommitRefsPresenter Grid.Column="2"
|
||||||
Background="{DynamicResource Brush.Contents}"
|
Background="{DynamicResource Brush.Contents}"
|
||||||
Foreground="{DynamicResource Brush.FG1}"
|
Foreground="{DynamicResource Brush.FG1}"
|
||||||
FontFamily="{DynamicResource Fonts.Primary}"
|
FontFamily="{DynamicResource Fonts.Primary}"
|
||||||
|
@ -153,7 +159,7 @@
|
||||||
</v:CommitRefsPresenter.UseGraphColor>
|
</v:CommitRefsPresenter.UseGraphColor>
|
||||||
</v:CommitRefsPresenter>
|
</v:CommitRefsPresenter>
|
||||||
|
|
||||||
<v:CommitSubjectPresenter Grid.Column="2"
|
<v:CommitSubjectPresenter Grid.Column="3"
|
||||||
Classes="primary"
|
Classes="primary"
|
||||||
Subject="{Binding Subject}"
|
Subject="{Binding Subject}"
|
||||||
IssueTrackerRules="{Binding $parent[v:Histories].IssueTrackerRules}"
|
IssueTrackerRules="{Binding $parent[v:Histories].IssueTrackerRules}"
|
||||||
|
|
|
@ -91,6 +91,15 @@ namespace SourceGit.Views
|
||||||
set => SetValue(CurrentBranchProperty, value);
|
set => SetValue(CurrentBranchProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<Models.Bisect> BisectProperty =
|
||||||
|
AvaloniaProperty.Register<Histories, Models.Bisect>(nameof(Bisect));
|
||||||
|
|
||||||
|
public Models.Bisect Bisect
|
||||||
|
{
|
||||||
|
get => GetValue(BisectProperty);
|
||||||
|
set => SetValue(BisectProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<AvaloniaList<Models.IssueTrackerRule>> IssueTrackerRulesProperty =
|
public static readonly StyledProperty<AvaloniaList<Models.IssueTrackerRule>> IssueTrackerRulesProperty =
|
||||||
AvaloniaProperty.Register<Histories, AvaloniaList<Models.IssueTrackerRule>>(nameof(IssueTrackerRules));
|
AvaloniaProperty.Register<Histories, AvaloniaList<Models.IssueTrackerRule>>(nameof(IssueTrackerRules));
|
||||||
|
|
||||||
|
|
|
@ -585,7 +585,7 @@
|
||||||
BorderBrush="{DynamicResource Brush.Border0}"/>
|
BorderBrush="{DynamicResource Brush.Border0}"/>
|
||||||
|
|
||||||
<!-- Right -->
|
<!-- Right -->
|
||||||
<Grid Grid.Column="2" RowDefinitions="Auto,Auto,*">
|
<Grid Grid.Column="2" RowDefinitions="Auto,Auto,Auto,*">
|
||||||
<Grid Grid.Row="0" Height="28" ColumnDefinitions="*,Auto" Background="{DynamicResource Brush.Conflict}" IsVisible="{Binding InProgressContext, Converter={x:Static ObjectConverters.IsNotNull}}">
|
<Grid Grid.Row="0" Height="28" ColumnDefinitions="*,Auto" Background="{DynamicResource Brush.Conflict}" IsVisible="{Binding InProgressContext, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||||
<ContentControl Grid.Column="0" Margin="8,0" Content="{Binding InProgressContext}">
|
<ContentControl Grid.Column="0" Margin="8,0" Content="{Binding InProgressContext}">
|
||||||
<ContentControl.DataTemplates>
|
<ContentControl.DataTemplates>
|
||||||
|
@ -665,7 +665,73 @@
|
||||||
Command="{Binding AbortMerge}"/>
|
Command="{Binding AbortMerge}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Border Grid.Row="1" Background="{DynamicResource Brush.ToolBar}" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}">
|
<Grid Grid.Row="1"
|
||||||
|
Height="28"
|
||||||
|
ColumnDefinitions="*,Auto,Auto,Auto,Auto,Auto"
|
||||||
|
Background="{DynamicResource Brush.Conflict}"
|
||||||
|
IsVisible="{Binding BisectState, Converter={x:Static ObjectConverters.NotEqual}, ConverterParameter={x:Static m:BisectState.None}}">
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Margin="8,0"
|
||||||
|
Foreground="{DynamicResource Brush.ConflictForeground}"
|
||||||
|
Text="{DynamicResource Text.Bisect.WaitingForRange}"
|
||||||
|
IsVisible="{Binding BisectState, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:BisectState.WaitingForRange}}"/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Margin="8,0"
|
||||||
|
Foreground="{DynamicResource Brush.ConflictForeground}"
|
||||||
|
Text="{DynamicResource Text.Bisect.Detecting}"
|
||||||
|
IsVisible="{Binding BisectState, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:BisectState.Detecting}}"/>
|
||||||
|
|
||||||
|
<v:LoadingIcon Grid.Column="1"
|
||||||
|
Width="14" Height="14"
|
||||||
|
Margin="4,0"
|
||||||
|
IsVisible="{Binding IsBisectCommandRunning}"/>
|
||||||
|
|
||||||
|
<Button Grid.Column="2"
|
||||||
|
Classes="flat"
|
||||||
|
Margin="4,0"
|
||||||
|
FontWeight="Regular"
|
||||||
|
BorderThickness="0"
|
||||||
|
Content="{DynamicResource Text.Bisect.Good}"
|
||||||
|
IsEnabled="{Binding !IsBisectCommandRunning}"
|
||||||
|
Padding="8,2"
|
||||||
|
Click="OnBisectCommand"
|
||||||
|
Tag="good"/>
|
||||||
|
<Button Grid.Column="3"
|
||||||
|
Classes="flat"
|
||||||
|
Margin="4,0"
|
||||||
|
FontWeight="Regular"
|
||||||
|
BorderThickness="0"
|
||||||
|
Content="{DynamicResource Text.Bisect.Bad}"
|
||||||
|
IsEnabled="{Binding !IsBisectCommandRunning}"
|
||||||
|
Padding="8,2"
|
||||||
|
Click="OnBisectCommand"
|
||||||
|
Tag="bad"/>
|
||||||
|
<Button Grid.Column="4"
|
||||||
|
Classes="flat"
|
||||||
|
Margin="4,0"
|
||||||
|
FontWeight="Regular"
|
||||||
|
BorderThickness="0"
|
||||||
|
Content="{DynamicResource Text.Bisect.Skip}"
|
||||||
|
IsEnabled="{Binding !IsBisectCommandRunning}"
|
||||||
|
Padding="8,2"
|
||||||
|
Click="OnBisectCommand"
|
||||||
|
Tag="skip"/>
|
||||||
|
<Button Grid.Column="5"
|
||||||
|
Classes="flat"
|
||||||
|
Margin="4,0"
|
||||||
|
FontWeight="Regular"
|
||||||
|
BorderThickness="0"
|
||||||
|
Content="{DynamicResource Text.Bisect.Abort}"
|
||||||
|
IsEnabled="{Binding !IsBisectCommandRunning}"
|
||||||
|
Padding="8,2"
|
||||||
|
Click="OnBisectCommand"
|
||||||
|
Tag="reset"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Border Grid.Row="2" Background="{DynamicResource Brush.ToolBar}" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}">
|
||||||
<Border.IsVisible>
|
<Border.IsVisible>
|
||||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||||
<Binding Path="SelectedViewIndex" Converter="{x:Static c:IntConverters.IsZero}"/>
|
<Binding Path="SelectedViewIndex" Converter="{x:Static c:IntConverters.IsZero}"/>
|
||||||
|
@ -721,10 +787,11 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<ContentControl Grid.Row="2" Content="{Binding SelectedView}">
|
<ContentControl Grid.Row="3" Content="{Binding SelectedView}">
|
||||||
<ContentControl.DataTemplates>
|
<ContentControl.DataTemplates>
|
||||||
<DataTemplate DataType="vm:Histories">
|
<DataTemplate DataType="vm:Histories">
|
||||||
<v:Histories CurrentBranch="{Binding Repo.CurrentBranch}"
|
<v:Histories CurrentBranch="{Binding Repo.CurrentBranch}"
|
||||||
|
Bisect="{Binding Bisect}"
|
||||||
AuthorNameColumnWidth="{Binding Source={x:Static vm:Preferences.Instance}, Path=Layout.HistoriesAuthorColumnWidth, Mode=TwoWay}"
|
AuthorNameColumnWidth="{Binding Source={x:Static vm:Preferences.Instance}, Path=Layout.HistoriesAuthorColumnWidth, Mode=TwoWay}"
|
||||||
IssueTrackerRules="{Binding Repo.Settings.IssueTrackerRules}"
|
IssueTrackerRules="{Binding Repo.Settings.IssueTrackerRules}"
|
||||||
OnlyHighlightCurrentBranch="{Binding Repo.OnlyHighlightCurrentBranchInHistories}"
|
OnlyHighlightCurrentBranch="{Binding Repo.OnlyHighlightCurrentBranchInHistories}"
|
||||||
|
|
|
@ -429,5 +429,15 @@ namespace SourceGit.Views
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnBisectCommand(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button &&
|
||||||
|
DataContext is ViewModels.Repository { IsBisectCommandRunning: false } repo &&
|
||||||
|
repo.CanCreatePopup())
|
||||||
|
repo.Bisect(button.Tag as string);
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,10 @@
|
||||||
<Path Width="14" Height="14" Data="{StaticResource Icons.LFS}"/>
|
<Path Width="14" Height="14" Data="{StaticResource Icons.LFS}"/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Button Classes="icon_button" Width="32" Margin="8,0,0,0" Click="StartBisect" ToolTip.Tip="{DynamicResource Text.Bisect}">
|
||||||
|
<Path Width="14" Height="14" Data="{StaticResource Icons.Bisect}"/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button Classes="icon_button" Width="32" Margin="8,0,0,0" Click="OpenCustomActionMenu" ToolTip.Tip="{DynamicResource Text.Repository.CustomActions}">
|
<Button Classes="icon_button" Width="32" Margin="8,0,0,0" Click="OpenCustomActionMenu" ToolTip.Tip="{DynamicResource Text.Repository.CustomActions}">
|
||||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Action}"/>
|
<Path Width="14" Height="14" Data="{StaticResource Icons.Action}"/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -116,6 +116,21 @@ namespace SourceGit.Views
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void StartBisect(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is ViewModels.Repository { IsBisectCommandRunning: false } repo &&
|
||||||
|
repo.InProgressContext == null &&
|
||||||
|
repo.CanCreatePopup())
|
||||||
|
{
|
||||||
|
if (repo.LocalChangesCount > 0)
|
||||||
|
App.RaiseException(repo.FullPath, "You have un-committed local changes. Please discard or stash them first.");
|
||||||
|
else
|
||||||
|
repo.Bisect("start");
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
private void OpenCustomActionMenu(object sender, RoutedEventArgs e)
|
private void OpenCustomActionMenu(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is ViewModels.Repository repo && sender is Control control)
|
if (DataContext is ViewModels.Repository repo && sender is Control control)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue