feature: supports to add ignored file(s) locally (saved in $GIT_DIR/info/exclude) (#1447)

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo 2025-06-23 17:22:57 +08:00
parent 5b6e9805dd
commit b519ead5a1
No known key found for this signature in database
9 changed files with 168 additions and 29 deletions

View file

@ -1,23 +0,0 @@
using System.IO;
namespace SourceGit.Commands
{
public static class GitIgnore
{
public static void Add(string repo, string pattern)
{
var file = Path.Combine(repo, ".gitignore");
if (!File.Exists(file))
{
File.WriteAllLines(file, [pattern]);
return;
}
var org = File.ReadAllText(file);
if (!org.EndsWith('\n'))
File.AppendAllLines(file, ["", pattern]);
else
File.AppendAllLines(file, [pattern]);
}
}
}

View file

@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.IO;
namespace SourceGit.Models
{
public class GitIgnoreFile
{
public static readonly List<GitIgnoreFile> Supported = [new(true), new(false)];
public bool IsShared { get; set; }
public string File => IsShared ? ".gitignore" : "$GIT_DIR/info/exclude";
public string Desc => IsShared ? "Shared" : "Private";
public GitIgnoreFile(bool isShared)
{
IsShared = isShared;
}
public string GetFullPath(string repoPath, string gitDir)
{
return IsShared ? Path.Combine(repoPath, ".gitignore") : Path.Combine(gitDir, "info", "exclude");
}
}
}

View file

@ -2,6 +2,9 @@
<x:String x:Key="Text.About" xml:space="preserve">About</x:String>
<x:String x:Key="Text.About.Menu" xml:space="preserve">About SourceGit</x:String>
<x:String x:Key="Text.About.SubTitle" xml:space="preserve">Opensource &amp; Free Git GUI Client</x:String>
<x:String x:Key="Text.AddToIgnore" xml:space="preserve">Add File(s) To Ignore</x:String>
<x:String x:Key="Text.AddToIgnore.Pattern" xml:space="preserve">Pattern:</x:String>
<x:String x:Key="Text.AddToIgnore.Storage" xml:space="preserve">Storage File:</x:String>
<x:String x:Key="Text.AddWorktree" xml:space="preserve">Add Worktree</x:String>
<x:String x:Key="Text.AddWorktree.Location" xml:space="preserve">Location:</x:String>
<x:String x:Key="Text.AddWorktree.Location.Placeholder" xml:space="preserve">Path for this worktree. Relative path is supported.</x:String>

View file

@ -6,6 +6,9 @@
<x:String x:Key="Text.About" xml:space="preserve">关于软件</x:String>
<x:String x:Key="Text.About.Menu" xml:space="preserve">关于本软件</x:String>
<x:String x:Key="Text.About.SubTitle" xml:space="preserve">开源免费的Git客户端</x:String>
<x:String x:Key="Text.AddToIgnore" xml:space="preserve">新增忽略文件</x:String>
<x:String x:Key="Text.AddToIgnore.Pattern" xml:space="preserve">匹配模式 </x:String>
<x:String x:Key="Text.AddToIgnore.Storage" xml:space="preserve">保存位置 </x:String>
<x:String x:Key="Text.AddWorktree" xml:space="preserve">新增工作树</x:String>
<x:String x:Key="Text.AddWorktree.Location" xml:space="preserve">工作树路径 </x:String>
<x:String x:Key="Text.AddWorktree.Location.Placeholder" xml:space="preserve">填写该工作树的路径。支持相对路径。</x:String>

View file

@ -6,6 +6,9 @@
<x:String x:Key="Text.About" xml:space="preserve">關於</x:String>
<x:String x:Key="Text.About.Menu" xml:space="preserve">關於 SourceGit</x:String>
<x:String x:Key="Text.About.SubTitle" xml:space="preserve">開源免費的 Git 客戶端</x:String>
<x:String x:Key="Text.AddToIgnore" xml:space="preserve">新增忽略檔案</x:String>
<x:String x:Key="Text.AddToIgnore.Pattern" xml:space="preserve">匹配模式 </x:String>
<x:String x:Key="Text.AddToIgnore.Storage" xml:space="preserve">儲存路徑 </x:String>
<x:String x:Key="Text.AddWorktree" xml:space="preserve">新增工作區</x:String>
<x:String x:Key="Text.AddWorktree.Location" xml:space="preserve">工作區路徑:</x:String>
<x:String x:Key="Text.AddWorktree.Location.Placeholder" xml:space="preserve">填寫該工作區的路徑。支援相對路徑。</x:String>

View file

@ -0,0 +1,64 @@
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Threading.Tasks;
namespace SourceGit.ViewModels
{
public class AddToIgnore : Popup
{
[Required(ErrorMessage = "Ignore pattern is required!")]
public string Pattern
{
get => _pattern;
set => SetProperty(ref _pattern, value, true);
}
[Required(ErrorMessage = "Storage file is required!!!")]
public Models.GitIgnoreFile StorageFile
{
get;
set;
}
public AddToIgnore(Repository repo, string pattern)
{
_repo = repo;
_pattern = pattern;
StorageFile = Models.GitIgnoreFile.Supported[0];
}
public override Task<bool> Sure()
{
_repo.SetWatcherEnabled(false);
ProgressDescription = "Adding Ignored File(s) ...";
return Task.Run(() =>
{
var file = StorageFile.GetFullPath(_repo.FullPath, _repo.GitDir);
if (!File.Exists(file))
{
File.WriteAllLines(file, [_pattern]);
}
else
{
var org = File.ReadAllText(file);
if (!org.EndsWith('\n'))
File.AppendAllLines(file, ["", _pattern]);
else
File.AppendAllLines(file, [_pattern]);
}
CallUIThread(() =>
{
_repo.MarkWorkingCopyDirtyManually();
_repo.SetWatcherEnabled(true);
});
return true;
});
}
private readonly Repository _repo;
private string _pattern;
}
}

View file

@ -771,7 +771,8 @@ namespace SourceGit.ViewModels
ignoreFolder.Header = App.Text("WorkingCopy.AddToGitIgnore.InFolder");
ignoreFolder.Click += (_, e) =>
{
Commands.GitIgnore.Add(_repo.FullPath, $"{selectedSingleFolder}/");
if (_repo.CanCreatePopup())
_repo.ShowPopup(new AddToIgnore(_repo, $"{selectedSingleFolder}/"));
e.Handled = true;
};
addToIgnore.Items.Add(ignoreFolder);
@ -783,7 +784,8 @@ namespace SourceGit.ViewModels
singleFile.Header = App.Text("WorkingCopy.AddToGitIgnore.SingleFile");
singleFile.Click += (_, e) =>
{
Commands.GitIgnore.Add(_repo.FullPath, change.Path);
if (_repo.CanCreatePopup())
_repo.ShowPopup(new AddToIgnore(_repo, change.Path));
e.Handled = true;
};
addToIgnore.Items.Add(singleFile);
@ -794,7 +796,8 @@ namespace SourceGit.ViewModels
byExtension.Header = App.Text("WorkingCopy.AddToGitIgnore.Extension", extension);
byExtension.Click += (_, e) =>
{
Commands.GitIgnore.Add(_repo.FullPath, $"*{extension}");
if (_repo.CanCreatePopup())
_repo.ShowPopup(new AddToIgnore(_repo, $"*{extension}"));
e.Handled = true;
};
addToIgnore.Items.Add(byExtension);
@ -805,7 +808,8 @@ namespace SourceGit.ViewModels
byExtensionInSameFolder.Click += (_, e) =>
{
var dir = Path.GetDirectoryName(change.Path)!.Replace('\\', '/').TrimEnd('/');
Commands.GitIgnore.Add(_repo.FullPath, $"{dir}/*{extension}");
if (_repo.CanCreatePopup())
_repo.ShowPopup(new AddToIgnore(_repo, $"{dir}/*{extension}"));
e.Handled = true;
};
addToIgnore.Items.Add(byExtensionInSameFolder);
@ -827,7 +831,8 @@ namespace SourceGit.ViewModels
ignoreFolder.Header = App.Text("WorkingCopy.AddToGitIgnore.InFolder");
ignoreFolder.Click += (_, e) =>
{
Commands.GitIgnore.Add(_repo.FullPath, $"{selectedSingleFolder}/");
if (_repo.CanCreatePopup())
_repo.ShowPopup(new AddToIgnore(_repo, $"{selectedSingleFolder}/"));
e.Handled = true;
};
addToIgnore.Items.Add(ignoreFolder);
@ -1133,7 +1138,8 @@ namespace SourceGit.ViewModels
ignoreFolder.Header = App.Text("WorkingCopy.AddToGitIgnore.InFolder");
ignoreFolder.Click += (_, e) =>
{
Commands.GitIgnore.Add(_repo.FullPath, $"{selectedSingleFolder}/");
if (_repo.CanCreatePopup())
_repo.ShowPopup(new AddToIgnore(_repo, $"{selectedSingleFolder}/"));
e.Handled = true;
};

View file

@ -0,0 +1,47 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:m="using:SourceGit.Models"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.AddToIgnore"
x:DataType="vm:AddToIgnore">
<StackPanel Orientation="Vertical" Margin="8,0">
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.AddToIgnore}"/>
<Grid Margin="0,16,0,0" RowDefinitions="32,32" ColumnDefinitions="120,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.AddToIgnore.Pattern}"/>
<TextBox Grid.Row="0" Grid.Column="1"
Height="28"
CornerRadius="3"
Text="{Binding Pattern, Mode=TwoWay}"
v:AutoFocusBehaviour.IsEnabled="True"/>
<TextBlock Grid.Row="1" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.AddToIgnore.Storage}"/>
<ComboBox Grid.Row="1" Grid.Column="1"
Height="28" Padding="8,0"
VerticalAlignment="Center" HorizontalAlignment="Stretch"
ItemsSource="{Binding Source={x:Static m:GitIgnoreFile.Supported}}"
SelectedItem="{Binding StorageFile, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="m:GitIgnoreFile">
<TextBlock VerticalAlignment="Center">
<Run Text="{Binding File, Mode=OneWay}"/>
<Run Text="•" Foreground="{DynamicResource Brush.FG2}"/>
<Run Text="{Binding Desc, Mode=OneWay}" Foreground="{DynamicResource Brush.FG2}"/>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</StackPanel>
</UserControl>

View file

@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace SourceGit.Views
{
public partial class AddToIgnore : UserControl
{
public AddToIgnore()
{
InitializeComponent();
}
}
}