mirror of
https://github.com/nsnail/dot.git
synced 2025-06-17 21:13:21 +08:00
<feat> + git批量操作工具
This commit is contained in:
parent
b3665aba40
commit
03213de766
89
src/Git/Main.cs
Normal file
89
src/Git/Main.cs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text;
|
||||||
|
using NSExt.Extensions;
|
||||||
|
|
||||||
|
namespace Dot.Git;
|
||||||
|
|
||||||
|
public class Main : ToolBase<Option>
|
||||||
|
{
|
||||||
|
private const int _POS_Y_MSG = 74;
|
||||||
|
private const int _POST_Y_LOADING = 70;
|
||||||
|
private const int _REP_MAX_LENGTH = 32;
|
||||||
|
private (int x, int y) _cursorInitPos;
|
||||||
|
private List<string> _dirList;
|
||||||
|
private Encoding _encGbk;
|
||||||
|
|
||||||
|
|
||||||
|
public Main(Option opt) : base(opt) { }
|
||||||
|
|
||||||
|
|
||||||
|
private async ValueTask DirHandle(string dir, CancellationToken cancelToken)
|
||||||
|
{
|
||||||
|
var index = _dirList.FindIndex(x => x == dir);
|
||||||
|
var tAnimate = LoadingAnimate(_POST_Y_LOADING, _cursorInitPos.y + index, out var cts);
|
||||||
|
|
||||||
|
void Write(object sender, DataReceivedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Data is null) return;
|
||||||
|
var msg = Encoding.UTF8.GetString(_encGbk.GetBytes(e.Data));
|
||||||
|
ConcurrentWrite(_POS_Y_MSG, _cursorInitPos.y + index, new string(' ', Console.WindowWidth - _POS_Y_MSG));
|
||||||
|
ConcurrentWrite(_POS_Y_MSG, _cursorInitPos.y + index, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var gitStartInfo = new ProcessStartInfo {
|
||||||
|
CreateNoWindow = true
|
||||||
|
, WorkingDirectory = dir
|
||||||
|
, FileName = "git"
|
||||||
|
, Arguments = Opt.Args
|
||||||
|
, UseShellExecute = false
|
||||||
|
, RedirectStandardOutput = true
|
||||||
|
, RedirectStandardError = true
|
||||||
|
};
|
||||||
|
using var p = Process.Start(gitStartInfo);
|
||||||
|
p.OutputDataReceived += Write;
|
||||||
|
p.ErrorDataReceived += Write;
|
||||||
|
p.BeginOutputReadLine();
|
||||||
|
p.BeginErrorReadLine();
|
||||||
|
await p.WaitForExitAsync();
|
||||||
|
|
||||||
|
|
||||||
|
cts.Cancel();
|
||||||
|
await tAnimate;
|
||||||
|
cts.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override async Task Run()
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(Opt.Path))
|
||||||
|
throw new ArgumentException(nameof(Opt.Path), string.Format(Str.PathNotFound, Opt.Path));
|
||||||
|
|
||||||
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
|
_encGbk = Encoding.GetEncoding("gbk");
|
||||||
|
|
||||||
|
Console.Write(Str.FindGitReps, Opt.Path);
|
||||||
|
_cursorInitPos = Console.GetCursorPosition();
|
||||||
|
var tAnimate = LoadingAnimate(_cursorInitPos.x, _cursorInitPos.y, out var cts);
|
||||||
|
|
||||||
|
_dirList = Directory.GetDirectories(Opt.Path, ".git", SearchOption.AllDirectories)
|
||||||
|
.Select(x => Directory.GetParent(x)!.FullName)
|
||||||
|
.ToList();
|
||||||
|
cts.Cancel();
|
||||||
|
await tAnimate;
|
||||||
|
|
||||||
|
cts.Dispose();
|
||||||
|
|
||||||
|
Console.WriteLine(Str.Ok);
|
||||||
|
_cursorInitPos = Console.GetCursorPosition();
|
||||||
|
var i = 0;
|
||||||
|
Console.WriteLine(string.Join(Environment.NewLine
|
||||||
|
, _dirList.Select(
|
||||||
|
x => $"{++i}: {new DirectoryInfo(x).Name.Sub(0, _REP_MAX_LENGTH)}")));
|
||||||
|
|
||||||
|
|
||||||
|
await Parallel.ForEachAsync(_dirList, DirHandle);
|
||||||
|
|
||||||
|
Console.SetCursorPosition(_cursorInitPos.x, _cursorInitPos.y + _dirList.Count);
|
||||||
|
}
|
||||||
|
}
|
11
src/Git/Option.cs
Normal file
11
src/Git/Option.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace Dot.Git;
|
||||||
|
|
||||||
|
[Verb("git", HelpText = nameof(Str.GitTool), ResourceType = typeof(Str))]
|
||||||
|
public class Option : OptionBase
|
||||||
|
{
|
||||||
|
[Option('a', "args", HelpText = nameof(Str.GitArgs), Default = "status", ResourceType = typeof(Str))]
|
||||||
|
public string Args { get; set; }
|
||||||
|
|
||||||
|
[Value(0, HelpText = nameof(Str.FolderPath), Default = ".", ResourceType = typeof(Str))]
|
||||||
|
public string Path { get; set; }
|
||||||
|
}
|
@ -30,7 +30,7 @@
|
|||||||
<value>The specified path "{0}" does not exist</value>
|
<value>The specified path "{0}" does not exist</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SearchingFileOK" xml:space="preserve">
|
<data name="SearchingFileOK" xml:space="preserve">
|
||||||
<value>Find files...OK</value>
|
<value>{0} files</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ShowMessageTemp" xml:space="preserve">
|
<data name="ShowMessageTemp" xml:space="preserve">
|
||||||
<value>Read: {0}/{1}, processed: {2}, skipped: {3}</value>
|
<value>Read: {0}/{1}, processed: {2}, skipped: {3}</value>
|
||||||
@ -137,4 +137,16 @@
|
|||||||
<data name="NtpServerTime" xml:space="preserve">
|
<data name="NtpServerTime" xml:space="preserve">
|
||||||
<value>NTP server standard clock: {0}</value>
|
<value>NTP server standard clock: {0}</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="GitTool" xml:space="preserve">
|
||||||
|
<value>Git batch operation tool</value>
|
||||||
|
</data>
|
||||||
|
<data name="GitArgs" xml:space="preserve">
|
||||||
|
<value>Parameters passed to Git</value>
|
||||||
|
</data>
|
||||||
|
<data name="Ok" xml:space="preserve">
|
||||||
|
<value>OK</value>
|
||||||
|
</data>
|
||||||
|
<data name="FindGitReps" xml:space="preserve">
|
||||||
|
<value>Find all git repository directories under "{0}"...</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -81,9 +81,15 @@
|
|||||||
<data name="GuidTool" xml:space="preserve">
|
<data name="GuidTool" xml:space="preserve">
|
||||||
<value>GUID工具</value>
|
<value>GUID工具</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="GitTool" xml:space="preserve">
|
||||||
|
<value>Git批量操作工具</value>
|
||||||
|
</data>
|
||||||
<data name="UseUppercase" xml:space="preserve">
|
<data name="UseUppercase" xml:space="preserve">
|
||||||
<value>使用大写输出</value>
|
<value>使用大写输出</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="GitArgs" xml:space="preserve">
|
||||||
|
<value>传递给Git的参数</value>
|
||||||
|
</data>
|
||||||
<data name="RandomPasswordGenerator" xml:space="preserve">
|
<data name="RandomPasswordGenerator" xml:space="preserve">
|
||||||
<value>随机密码生成器</value>
|
<value>随机密码生成器</value>
|
||||||
</data>
|
</data>
|
||||||
@ -145,4 +151,10 @@
|
|||||||
<data name="PublicIP" xml:space="preserve">
|
<data name="PublicIP" xml:space="preserve">
|
||||||
<value>Public network ip... </value>
|
<value>Public network ip... </value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Ok" xml:space="preserve">
|
||||||
|
<value>OK</value>
|
||||||
|
</data>
|
||||||
|
<data name="FindGitReps" xml:space="preserve">
|
||||||
|
<value>查找 "{0}" 下所有git仓库目录... </value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -1,7 +1,7 @@
|
|||||||
<#@ template language="C#" #>
|
<#@ template language="C#" #>
|
||||||
<#@ import namespace="System.Xml" #>
|
|
||||||
<#@ assembly name="System.Xml" #>
|
<#@ assembly name="System.Xml" #>
|
||||||
<#@ output encoding="utf-8" extension="Designer.cs" #>
|
<#@ output encoding="utf-8" extension="Designer.cs" #>
|
||||||
|
<#@ import namespace="System.Xml" #>
|
||||||
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@ -69,7 +69,7 @@ namespace Dot.Lang {
|
|||||||
var xml = new XmlDocument();
|
var xml = new XmlDocument();
|
||||||
xml.Load("Str.resx");
|
xml.Load("Str.resx");
|
||||||
foreach (XmlNode data in xml.SelectNodes("//root/data")) {
|
foreach (XmlNode data in xml.SelectNodes("//root/data")) {
|
||||||
#>
|
#>
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <#= data.SelectSingleNode("value").InnerText #>
|
/// <#= data.SelectSingleNode("value").InnerText #>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -12,8 +12,9 @@ Type[] LoadVerbs()
|
|||||||
|
|
||||||
async Task Run(object args)
|
async Task Run(object args)
|
||||||
{
|
{
|
||||||
var option = args as OptionBase;
|
if (args is not OptionBase option) return;
|
||||||
var tool = ToolsFactory.Create(option);
|
|
||||||
|
var tool = ToolsFactory.Create(option);
|
||||||
await tool.Run();
|
await tool.Run();
|
||||||
if (option!.KeepSession) {
|
if (option!.KeepSession) {
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
@ -2,6 +2,8 @@ namespace Dot;
|
|||||||
|
|
||||||
public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
|
public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
|
||||||
{
|
{
|
||||||
|
private static readonly object _lockObj = new();
|
||||||
|
|
||||||
protected readonly ProgressBarOptions //
|
protected readonly ProgressBarOptions //
|
||||||
DefaultProgressBarOptions = new() {
|
DefaultProgressBarOptions = new() {
|
||||||
MessageEncodingName = "utf-8"
|
MessageEncodingName = "utf-8"
|
||||||
@ -20,6 +22,15 @@ public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
|
|||||||
Opt = opt;
|
Opt = opt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected static void ConcurrentWrite(int x, int y, string text)
|
||||||
|
{
|
||||||
|
lock (_lockObj) {
|
||||||
|
Console.SetCursorPosition(x, y);
|
||||||
|
Console.Write(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected static IEnumerable<string> EnumerateFiles(string path, string searchPattern)
|
protected static IEnumerable<string> EnumerateFiles(string path, string searchPattern)
|
||||||
{
|
{
|
||||||
var fileList = Directory
|
var fileList = Directory
|
||||||
@ -33,6 +44,28 @@ public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
|
|||||||
return fileList;
|
return fileList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static Task LoadingAnimate(int x, int y, out CancellationTokenSource cts)
|
||||||
|
{
|
||||||
|
char[] animateChars = { '-', '\\', '|', '/' };
|
||||||
|
long counter = 0;
|
||||||
|
|
||||||
|
cts = new CancellationTokenSource();
|
||||||
|
var cancelToken = cts.Token;
|
||||||
|
|
||||||
|
return Task.Run(async () => {
|
||||||
|
for (;;) {
|
||||||
|
if (cancelToken.IsCancellationRequested) {
|
||||||
|
ConcurrentWrite(x, y, @" ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConcurrentWrite(x, y, animateChars[counter++ % 4].ToString());
|
||||||
|
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected static void MoveFile(string source, string dest)
|
protected static void MoveFile(string source, string dest)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@ -65,6 +98,5 @@ public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
|
|||||||
return fsr;
|
return fsr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public abstract Task Run();
|
public abstract Task Run();
|
||||||
}
|
}
|
@ -16,6 +16,7 @@ public static class ToolsFactory
|
|||||||
, Time.Option o => new Time.Main(o)
|
, Time.Option o => new Time.Main(o)
|
||||||
, Color.Option o => new Color.Main(o)
|
, Color.Option o => new Color.Main(o)
|
||||||
, IP.Option o => new IP.Main(o)
|
, IP.Option o => new IP.Main(o)
|
||||||
|
, Git.Option o => new Git.Main(o)
|
||||||
, _ => throw new ArgumentOutOfRangeException(nameof(option))
|
, _ => throw new ArgumentOutOfRangeException(nameof(option))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<RootNamespace>Dot</RootNamespace>
|
<RootNamespace>Dot</RootNamespace>
|
||||||
<AssemblyName>dot</AssemblyName>
|
<AssemblyName>dot</AssemblyName>
|
||||||
<Version>1.1.3</Version>
|
<Version>1.1.4</Version>
|
||||||
<Authors>nsnail</Authors>
|
<Authors>nsnail</Authors>
|
||||||
<Copyright>Copyright (c) 2022 nsnail</Copyright>
|
<Copyright>Copyright (c) 2022 nsnail</Copyright>
|
||||||
<RepositoryUrl>https://github.com/nsnail/dot.git</RepositoryUrl>
|
<RepositoryUrl>https://github.com/nsnail/dot.git</RepositoryUrl>
|
||||||
@ -24,10 +24,10 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
<PackageReference Include="CommandLineParser" Version="2.9.1"/>
|
||||||
<PackageReference Include="NSExt" Version="1.0.6" />
|
<PackageReference Include="NSExt" Version="1.0.6"/>
|
||||||
<PackageReference Include="ShellProgressBar" Version="5.2.0" />
|
<PackageReference Include="ShellProgressBar" Version="5.2.0"/>
|
||||||
<PackageReference Include="TextCopy" Version="6.2.0" />
|
<PackageReference Include="TextCopy" Version="6.2.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
@ -37,7 +37,7 @@
|
|||||||
<LastGenOutput>Str.Designer.cs</LastGenOutput>
|
<LastGenOutput>Str.Designer.cs</LastGenOutput>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Lang\Str.Designer.cs">
|
<Compile Update="Lang\Str.Designer.cs">
|
||||||
<DesignTime>True</DesignTime>
|
<DesignTime>True</DesignTime>
|
||||||
@ -48,10 +48,10 @@
|
|||||||
|
|
||||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||||
<ItemGroup Condition="!Exists('Lang\Str.Designer.cs')">
|
<ItemGroup Condition="!Exists('Lang\Str.Designer.cs')">
|
||||||
<Compile Include="Lang\Str.Designer.cs" />
|
<Compile Include="Lang\Str.Designer.cs"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Exec Command="dotnet tool restore" />
|
<Exec Command="dotnet tool restore"/>
|
||||||
<Exec WorkingDirectory="$(ProjectDir)\Lang" Command="dotnet t4 Str.tt" />
|
<Exec WorkingDirectory="$(ProjectDir)\Lang" Command="dotnet t4 Str.tt"/>
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
Loading…
x
Reference in New Issue
Block a user