2021-04-29 05:05:55 -07:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.IO;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
using System.Windows;
|
|
|
|
using System.Windows.Controls;
|
|
|
|
using System.Windows.Controls.Primitives;
|
|
|
|
|
|
|
|
namespace SourceGit.Views.Widgets {
|
|
|
|
/// <summary>
|
|
|
|
/// 工作区
|
|
|
|
/// </summary>
|
|
|
|
public partial class WorkingCopy : UserControl {
|
|
|
|
private Models.Repository repo = null;
|
2021-06-06 23:14:53 -07:00
|
|
|
private bool isLFSEnabled = false;
|
2021-04-29 05:05:55 -07:00
|
|
|
|
|
|
|
public string CommitMessage { get; set; }
|
|
|
|
|
|
|
|
public WorkingCopy(Models.Repository repo) {
|
|
|
|
this.repo = repo;
|
2021-06-06 23:14:53 -07:00
|
|
|
this.isLFSEnabled = new Commands.LFS(repo.Path).IsEnabled();
|
2021-04-29 05:05:55 -07:00
|
|
|
|
|
|
|
InitializeComponent();
|
|
|
|
|
|
|
|
unstagedContainer.SetRepository(repo.Path);
|
|
|
|
stagedContainer.SetRepository(repo.Path);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void SetData(List<Models.Change> changes) {
|
|
|
|
List<Models.Change> unstagedChanges = new List<Models.Change>();
|
|
|
|
List<Models.Change> stagedChanges = new List<Models.Change>();
|
|
|
|
|
|
|
|
foreach (var c in changes) {
|
2021-05-07 19:08:57 -07:00
|
|
|
if (c.Index == Models.Change.Status.Modified
|
|
|
|
|| c.Index == Models.Change.Status.Added
|
|
|
|
|| c.Index == Models.Change.Status.Deleted
|
|
|
|
|| c.Index == Models.Change.Status.Renamed) {
|
2021-04-29 05:05:55 -07:00
|
|
|
stagedChanges.Add(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c.WorkTree != Models.Change.Status.None) {
|
|
|
|
unstagedChanges.Add(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unstagedContainer.SetData(unstagedChanges);
|
|
|
|
stagedContainer.SetData(stagedChanges);
|
|
|
|
|
|
|
|
var current = repo.Branches.Find(x => x.IsCurrent);
|
|
|
|
if (current != null && !string.IsNullOrEmpty(current.Upstream) && chkAmend.IsChecked != true) {
|
|
|
|
btnCommitAndPush.Visibility = Visibility.Visible;
|
|
|
|
} else {
|
|
|
|
btnCommitAndPush.Visibility = Visibility.Collapsed;
|
|
|
|
}
|
|
|
|
|
|
|
|
var diffTarget = unstagedContainer.DiffTarget;
|
|
|
|
if (diffTarget == null) diffTarget = stagedContainer.DiffTarget;
|
|
|
|
if (diffTarget == null) {
|
|
|
|
mergePanel.Visibility = Visibility.Collapsed;
|
|
|
|
diffViewer.Reset();
|
|
|
|
} else if (diffTarget.IsConflit) {
|
|
|
|
mergePanel.Visibility = Visibility.Visible;
|
|
|
|
diffViewer.Reset();
|
|
|
|
} else {
|
|
|
|
mergePanel.Visibility = Visibility.Collapsed;
|
|
|
|
diffViewer.Reload();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void TryLoadMergeMessage() {
|
|
|
|
if (string.IsNullOrEmpty(txtCommitMessage.Text)) {
|
|
|
|
var mergeMsgFile = Path.Combine(repo.GitDir, "MERGE_MSG");
|
|
|
|
if (!File.Exists(mergeMsgFile)) return;
|
|
|
|
|
|
|
|
var content = File.ReadAllText(mergeMsgFile);
|
|
|
|
txtCommitMessage.Text = content;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void ClearMessage() {
|
|
|
|
txtCommitMessage.Text = "";
|
|
|
|
Validation.ClearInvalid(txtCommitMessage.GetBindingExpression(TextBox.TextProperty));
|
|
|
|
}
|
|
|
|
|
|
|
|
#region STAGE_UNSTAGE
|
|
|
|
private void StageSelected(object sender, RoutedEventArgs e) {
|
|
|
|
unstagedContainer.StageSelected();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void StageAll(object sender, RoutedEventArgs e) {
|
|
|
|
unstagedContainer.StageAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void UnstageSelected(object sender, RoutedEventArgs e) {
|
|
|
|
stagedContainer.UnstageSelected();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void UnstageAll(object sender, RoutedEventArgs e) {
|
|
|
|
stagedContainer.UnstageAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void OnDiffTargetChanged(object sender, WorkingCopyChanges.DiffTargetChangedEventArgs e) {
|
|
|
|
var container = sender as WorkingCopyChanges;
|
|
|
|
if (container == null) return;
|
|
|
|
|
|
|
|
if (e.Target == null) {
|
|
|
|
if (e.HasOthers) {
|
2021-04-29 07:35:02 -07:00
|
|
|
if (container.IsUnstaged) {
|
|
|
|
stagedContainer.UnselectAll();
|
|
|
|
} else {
|
|
|
|
unstagedContainer.UnselectAll();
|
|
|
|
}
|
|
|
|
|
2021-04-29 05:05:55 -07:00
|
|
|
mergePanel.Visibility = Visibility.Collapsed;
|
|
|
|
diffViewer.Reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-05-07 18:48:53 -07:00
|
|
|
if (container.IsUnstaged) {
|
|
|
|
stagedContainer.UnselectAll();
|
|
|
|
} else {
|
|
|
|
unstagedContainer.UnselectAll();
|
|
|
|
}
|
|
|
|
|
2021-04-29 05:05:55 -07:00
|
|
|
var change = e.Target;
|
|
|
|
if (change.IsConflit) {
|
|
|
|
mergePanel.Visibility = Visibility.Visible;
|
2021-05-07 18:48:53 -07:00
|
|
|
diffViewer.Reset();
|
2021-04-29 05:05:55 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-04-29 07:35:02 -07:00
|
|
|
mergePanel.Visibility = Visibility.Collapsed;
|
2021-05-07 18:48:53 -07:00
|
|
|
if (container.IsUnstaged) {
|
2021-04-29 05:05:55 -07:00
|
|
|
switch (change.WorkTree) {
|
|
|
|
case Models.Change.Status.Added:
|
|
|
|
case Models.Change.Status.Untracked:
|
|
|
|
diffViewer.Diff(repo.Path, new DiffViewer.Option() {
|
|
|
|
ExtraArgs = "--no-index",
|
|
|
|
Path = change.Path,
|
2021-06-06 23:14:53 -07:00
|
|
|
OrgPath = "/dev/null",
|
|
|
|
UseLFS = isLFSEnabled
|
2021-04-29 05:05:55 -07:00
|
|
|
});
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
diffViewer.Diff(repo.Path, new DiffViewer.Option() {
|
|
|
|
Path = change.Path,
|
2021-06-06 23:14:53 -07:00
|
|
|
OrgPath = change.OriginalPath,
|
|
|
|
UseLFS = isLFSEnabled
|
2021-04-29 05:05:55 -07:00
|
|
|
});
|
|
|
|
break;
|
|
|
|
}
|
2021-05-07 18:48:53 -07:00
|
|
|
} else {
|
|
|
|
diffViewer.Diff(repo.Path, new DiffViewer.Option() {
|
|
|
|
ExtraArgs = "--cached",
|
|
|
|
Path = change.Path,
|
2021-06-06 23:14:53 -07:00
|
|
|
OrgPath = change.OriginalPath,
|
|
|
|
UseLFS = isLFSEnabled
|
2021-05-07 18:48:53 -07:00
|
|
|
});
|
2021-04-29 05:05:55 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region MERGE
|
|
|
|
private async void UseTheirs(object sender, RoutedEventArgs e) {
|
|
|
|
var change = unstagedContainer.DiffTarget;
|
|
|
|
if (change == null || !change.IsConflit) return;
|
|
|
|
|
|
|
|
Models.Watcher.SetEnabled(repo.Path, false);
|
|
|
|
var succ = await Task.Run(() => new Commands.Checkout(repo.Path).File(change.Path, true));
|
|
|
|
if (succ) {
|
|
|
|
await Task.Run(() => new Commands.Add(repo.Path, new List<string>() { change.Path }).Exec());
|
|
|
|
}
|
|
|
|
Models.Watcher.SetEnabled(repo.Path, true);
|
|
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private async void UseMine(object sender, RoutedEventArgs e) {
|
|
|
|
var change = unstagedContainer.DiffTarget;
|
|
|
|
if (change == null || !change.IsConflit) return;
|
|
|
|
|
|
|
|
Models.Watcher.SetEnabled(repo.Path, false);
|
|
|
|
var succ = await Task.Run(() => new Commands.Checkout(repo.Path).File(change.Path, false));
|
|
|
|
if (succ) {
|
|
|
|
await Task.Run(() => new Commands.Add(repo.Path, new List<string>() { change.Path }).Exec());
|
|
|
|
}
|
|
|
|
Models.Watcher.SetEnabled(repo.Path, true);
|
|
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private async void UseMergeTool(object sender, RoutedEventArgs e) {
|
|
|
|
var mergeType = Models.Preference.Instance.MergeTool.Type;
|
|
|
|
var mergeExe = Models.Preference.Instance.MergeTool.Path;
|
|
|
|
|
|
|
|
var merger = Models.MergeTool.Supported.Find(x => x.Type == mergeType);
|
|
|
|
if (merger == null || merger.Type == 0 || !File.Exists(mergeExe)) {
|
|
|
|
Models.Exception.Raise("Invalid merge tool in preference setting!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var change = unstagedContainer.DiffTarget;
|
|
|
|
if (change == null || !change.IsConflit) return;
|
|
|
|
|
|
|
|
var cmd = new Commands.Command();
|
|
|
|
cmd.Cwd = repo.Path;
|
2021-05-28 05:49:43 -07:00
|
|
|
cmd.DontRaiseError = true;
|
2021-04-29 05:05:55 -07:00
|
|
|
cmd.Args = $"-c mergetool.sourcegit.cmd=\"\\\"{mergeExe}\\\" {merger.Cmd}\" ";
|
|
|
|
cmd.Args += "-c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true ";
|
|
|
|
cmd.Args += $"mergetool --tool=sourcegit {change.Path}";
|
|
|
|
|
|
|
|
await Task.Run(() => cmd.Exec());
|
|
|
|
e.Handled = true;
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region COMMIT
|
|
|
|
private void OpenCommitMessageRecorder(object sender, RoutedEventArgs e) {
|
|
|
|
var anchor = sender as Button;
|
|
|
|
|
|
|
|
if (anchor.ContextMenu == null) {
|
|
|
|
anchor.ContextMenu = new ContextMenu();
|
|
|
|
anchor.ContextMenu.PlacementTarget = anchor;
|
|
|
|
anchor.ContextMenu.Placement = PlacementMode.Top;
|
|
|
|
anchor.ContextMenu.VerticalOffset = 0;
|
|
|
|
anchor.ContextMenu.StaysOpen = false;
|
|
|
|
anchor.ContextMenu.Focusable = true;
|
|
|
|
anchor.ContextMenu.MaxWidth = 500;
|
|
|
|
} else {
|
|
|
|
anchor.ContextMenu.Items.Clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (repo.CommitMessages.Count == 0) {
|
|
|
|
var tip = new MenuItem();
|
|
|
|
tip.Header = App.Text("WorkingCopy.NoCommitHistories");
|
|
|
|
tip.IsEnabled = false;
|
|
|
|
anchor.ContextMenu.Items.Add(tip);
|
|
|
|
} else {
|
|
|
|
var tip = new MenuItem();
|
|
|
|
tip.Header = App.Text("WorkingCopy.HasCommitHistories");
|
|
|
|
tip.IsEnabled = false;
|
|
|
|
anchor.ContextMenu.Items.Add(tip);
|
|
|
|
anchor.ContextMenu.Items.Add(new Separator());
|
|
|
|
|
|
|
|
foreach (var one in repo.CommitMessages) {
|
|
|
|
var dump = one;
|
|
|
|
|
|
|
|
var item = new MenuItem();
|
|
|
|
item.Header = dump;
|
|
|
|
item.Padding = new Thickness(0);
|
|
|
|
item.Click += (o, ev) => {
|
|
|
|
txtCommitMessage.Text = dump;
|
|
|
|
ev.Handled = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
anchor.ContextMenu.Items.Add(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
anchor.ContextMenu.IsOpen = true;
|
|
|
|
e.Handled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void StartAmend(object sender, RoutedEventArgs e) {
|
|
|
|
var commits = new Commands.Commits(repo.Path, "-n 1", false).Result();
|
|
|
|
if (commits.Count == 0) {
|
|
|
|
Models.Exception.Raise("No commits to amend!");
|
|
|
|
chkAmend.IsChecked = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
txtCommitMessage.Text = commits[0].Subject;
|
|
|
|
btnCommitAndPush.Visibility = Visibility.Collapsed;
|
|
|
|
e.Handled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void EndAmend(object sender, RoutedEventArgs e) {
|
|
|
|
if (!IsLoaded) return;
|
|
|
|
|
|
|
|
var current = repo.Branches.Find(x => x.IsCurrent);
|
|
|
|
if (current != null && !string.IsNullOrEmpty(current.Upstream)) {
|
|
|
|
btnCommitAndPush.Visibility = Visibility.Visible;
|
|
|
|
} else {
|
|
|
|
btnCommitAndPush.Visibility = Visibility.Collapsed;
|
|
|
|
}
|
|
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private async void Commit(object sender, RoutedEventArgs e) {
|
|
|
|
var changes = await Task.Run(() => new Commands.LocalChanges(repo.Path).Result());
|
|
|
|
var conflict = changes.Find(x => x.IsConflit);
|
|
|
|
if (conflict != null) {
|
|
|
|
Models.Exception.Raise("You have unsolved conflicts in your working copy!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stagedContainer.Changes.Count == 0) {
|
|
|
|
Models.Exception.Raise("No files added to commit!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
txtCommitMessage.GetBindingExpression(TextBox.TextProperty).UpdateSource();
|
|
|
|
if (Validation.GetHasError(txtCommitMessage)) return;
|
|
|
|
|
|
|
|
repo.PushCommitMessage(CommitMessage);
|
|
|
|
iconCommitting.Visibility = Visibility.Visible;
|
|
|
|
iconCommitting.IsAnimating = true;
|
|
|
|
|
|
|
|
Models.Watcher.SetEnabled(repo.Path, false);
|
|
|
|
var amend = chkAmend.IsChecked == true;
|
|
|
|
var succ = await Task.Run(() => new Commands.Commit(repo.Path, CommitMessage, amend).Exec());
|
|
|
|
if (succ) ClearMessage();
|
|
|
|
iconCommitting.IsAnimating = false;
|
|
|
|
iconCommitting.Visibility = Visibility.Collapsed;
|
|
|
|
Models.Watcher.SetEnabled(repo.Path, true);
|
|
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private async void CommitAndPush(object sender, RoutedEventArgs e) {
|
|
|
|
var changes = await Task.Run(() => new Commands.LocalChanges(repo.Path).Result());
|
|
|
|
var conflict = changes.Find(x => x.IsConflit);
|
|
|
|
if (conflict != null) {
|
|
|
|
Models.Exception.Raise("You have unsolved conflicts in your working copy!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stagedContainer.Changes.Count == 0) {
|
|
|
|
Models.Exception.Raise("No files added to commit!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
txtCommitMessage.GetBindingExpression(TextBox.TextProperty).UpdateSource();
|
|
|
|
if (Validation.GetHasError(txtCommitMessage)) return;
|
|
|
|
|
|
|
|
repo.PushCommitMessage(CommitMessage);
|
|
|
|
iconCommitting.Visibility = Visibility.Visible;
|
|
|
|
iconCommitting.IsAnimating = true;
|
|
|
|
|
|
|
|
Models.Watcher.SetEnabled(repo.Path, false);
|
|
|
|
var succ = await Task.Run(() => new Commands.Commit(repo.Path, CommitMessage, false).Exec());
|
|
|
|
if (succ) {
|
|
|
|
new Popups.Push(repo, repo.Branches.Find(x => x.IsCurrent)).ShowAndStart();
|
|
|
|
ClearMessage();
|
|
|
|
}
|
|
|
|
iconCommitting.IsAnimating = false;
|
|
|
|
iconCommitting.Visibility = Visibility.Collapsed;
|
|
|
|
Models.Watcher.SetEnabled(repo.Path, true);
|
|
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|