/// <summary> /// Start scanning at the time the form is first shown /// </summary> private void FormNewRepoScanAddShown(object sender, EventArgs e) { enableAdd = true; int count = 1; // Create each of the repos for the selected directories foreach (var d in dirs) { if (enableAdd == false) { return; } try { // Update progress bar and make sure it gets painted progressBar.Value = count++; Thread.Sleep(1); textRepo.Text = d; Application.DoEvents(); Directory.SetCurrentDirectory(d); if (ClassGit.Run("init").Success() == false) { throw new ClassException("init failed."); } App.Repos.Add(d); } catch (Exception ex) { App.PrintLogMessage("Unable to add repo: " + ex.Message, MessageType.Error); App.PrintStatusMessage(ex.Message, MessageType.Error); } } DialogResult = DialogResult.OK; }
/// <summary> /// Constructor creates a shell executable file that echoes the PASSWORD /// environment variable when called. When GIT_ASKPASS is present, Git /// obtains a password for its HTTPS operations by calling it. /// </summary> public ClassGitPasswd() { // WAR: Do a different kind of shell script dependent on the OS) if (ClassUtils.IsMono()) { // Mono: Use the Shell script pathPasswordBatchHelper = Path.Combine(App.AppHome, "passwd.sh"); File.WriteAllText(pathPasswordBatchHelper, "echo $PASSWORD" + Environment.NewLine); // Set the execute bit if (Exec.Run("chmod", "+x " + pathPasswordBatchHelper).Success() == false) { App.PrintLogMessage("ClassGitPasswd: Unable to chmod +x on " + pathPasswordBatchHelper, MessageType.Error); } } else { // Windows: Use the CMD BAT script // Note: Using "App.AppHome" directory to host the batch helper file // fails on XP where that directory has spaces in the name ("Documents and Settings") // which git cannot handle in this context. Similarly, git will fail with // any other path that contains a space. // This redirection is used to provide the password in an automated way. pathPasswordBatchHelper = Path.Combine(Path.GetTempPath(), "passwd.bat"); File.WriteAllText(pathPasswordBatchHelper, "@echo %PASSWORD%" + Environment.NewLine); pathPasswordBatchHelper = ClassUtils.GetShortPathName(pathPasswordBatchHelper); } ClassUtils.AddEnvar("GIT_ASKPASS", pathPasswordBatchHelper); App.PrintLogMessage("Created HTTP password helper file: " + pathPasswordBatchHelper, MessageType.General); }
/// <summary> /// Open a file browser/Explorer at the specific directory, optionally selecting a file /// </summary> public static void ExplorerHere(string where, string selFile) { try { App.PrintStatusMessage("Opening a file browser at " + where, MessageType.General); // WAR: Opening an "Explorer" is platform-specific if (IsMono()) { // TODO: Start a Linux (Ubuntu?) file explorer in a more flexible way Process.Start(@"/usr/bin/nautilus", "--browser " + where); } else { string path = selFile == string.Empty ? "/e,\"" + where + "\"" : "/e, /select,\"" + selFile + "\""; App.PrintLogMessage(path, MessageType.Command); Process.Start("explorer.exe", path); } } catch (Exception ex) { App.PrintLogMessage(ex.Message, MessageType.Error); MessageBox.Show(ex.Message, "Explorer Here error", MessageBoxButtons.OK, MessageBoxIcon.Error); } }
/// <summary> /// User clicked on the OK button, format the git filter string /// </summary> private void BtOkClick(object sender, EventArgs e) { gitFilter = ""; if (checkBoxMessage.Checked) { gitFilter += " --grep=\"" + textBoxMessage.Text + "\""; } if (checkBoxAuthor.Checked) { gitFilter += " --author=\"" + textBoxAuthor.Text + "\""; } if (checkBoxBefore.Checked) { DateTime dt = dateTimeBefore.Value; gitFilter += " --before=" + String.Format("{0:yyyy/MM/dd}", dt); } if (checkBoxAfter.Checked) { DateTime dt = dateTimeAfter.Value; gitFilter += " --after=" + String.Format("{0:yyyy/MM/dd}", dt); } App.PrintLogMessage("Condition: " + gitFilter, MessageType.General); }
/// <summary> /// Remove given folder and all files and subfolders under it. /// If fPreserveGit is true, all folders that are named ".git" will be preserved (not removed) /// If fPreserveRootFolder is true, the first (root) folder will also be preserved /// Return false if the function could not remove all folders, true otherwise. /// </summary> public static bool DeleteFolder(DirectoryInfo dirInfo, bool fPreserveGit, bool fPreserveRootFolder) { bool f = true; try { foreach (var subDir in dirInfo.GetDirectories()) { if (fPreserveGit == false || !subDir.Name.EndsWith(".git")) { f &= DeleteFolder(subDir, false, false); } } foreach (var file in dirInfo.GetFiles()) { f &= DeleteFile(file.FullName); } if (fPreserveRootFolder == false) { f &= DeleteFolder(dirInfo); } } catch (Exception ex) { App.PrintLogMessage("Error deleting directory " + dirInfo.FullName + ": " + ex.Message, MessageType.Error); } return(f); }
/// <summary> /// Callback that handles process printing to stderr /// Collect all strings into one stderr variable and call a custom handler. /// </summary> private void PErrorDataReceived(object sender, DataReceivedEventArgs e) { if (String.IsNullOrEmpty(e.Data)) // If the stream ended { // Sometimes we receive multiple null strings on error stream // (example: when adding a new key with plink) // This catches these cases which would increment semaphore over it's limit try { Exited.Release(); // release its semaphore } catch (Exception ex) { App.PrintLogMessage(ex.Message, MessageType.Error); } } else { if (Result.stderr != string.Empty) { Result.stderr += Environment.NewLine; } Result.stderr += e.Data; if (FStderr != null) { App.MainForm.BeginInvoke((MethodInvoker)(() => FStderr(e.Data))); } } }
/// <summary> /// Saves the current set of repositories to a file. /// Returns true if save succeeded. /// </summary> public bool Save(string fileName) { try { using (FileStream file = new FileStream(fileName, FileMode.Create)) { try { BinaryFormatter wr = new BinaryFormatter(); wr.Serialize(file, Repos); wr.Serialize(file, Default == null ? "" : Default.Path); return(true); } catch (Exception ex) { throw new ClassException(ex.Message); } } } catch (Exception ex) { App.PrintLogMessage(ex.Message, MessageType.Error); } return(false); }
/// <summary> /// Recursively create a list of directories and files from the given path. /// </summary> public static List <string> GetFilesRecursive(string path) { List <string> result = new List <string>(); Stack <string> stack = new Stack <string>(); stack.Push(path); while (stack.Count > 0) { string dir = stack.Pop(); try { result.AddRange(Directory.GetFiles(dir, "*.*")); foreach (string d in Directory.GetDirectories(dir).Where(d => !d.EndsWith(Path.DirectorySeparatorChar + ".git") || Properties.Settings.Default.ShowDotGitFolders)) { stack.Push(d); } } catch (Exception ex) { App.PrintLogMessage(ex.Message, MessageType.Error); } } return(result); }
/// <summary> /// Print into the status pane (and the aux log window). /// It is ok to send null or empty string. /// Strings that contain Newline will be broken into separate lines. /// This function is thread-safe. /// </summary> private void PrintStatus(string message, MessageType type) { if (string.IsNullOrEmpty(message)) { return; } if (listStatus.InvokeRequired) { listStatus.BeginInvoke((MethodInvoker)(() => PrintStatus(message, type))); } else { // Add each line of the message individually // For performance reasons, only up to 250 characters of text are added in one call. foreach (string line in message.Split(new[] { Environment.NewLine }, StringSplitOptions.None)) { // Prepend the current time, if that option is requested, in either 12 or 24-hr format string stamp = Properties.Settings.Default.logTime ? DateTime.Now.ToString(Properties.Settings.Default.logTime24 ? "HH:mm:ss" : "hh:mm:ss") + " " : ""; int len = Math.Min(line.Length, 250); listStatus.Items.Add(new StatusListBoxItem(stamp + line.Substring(0, len), type)); } listStatus.TopIndex = listStatus.Items.Count - 1; App.PrintLogMessage(message, type); } }
public ExecResult Run(string args, bool async) { ExecResult output = new ExecResult(); try { Directory.SetCurrentDirectory(Root); // Set the HTTPS password string password = Remotes.GetPassword(""); ClassUtils.AddEnvar("PASSWORD", password); // The Windows limit to the command line argument length is about 8K // We may hit that limit when doing operations on a large number of files. // // However, when sending a long list of files, git was hanging unless // the total length was much less than that, so I set it to about 2000 chars // which seemed to work fine. if (args.Length < 2000) { return(ClassGit.Run(args, async)); } // Partition the args into "[command] -- [set of file chunks < 2000 chars]" // Basically we have to rebuild the command into multiple instances with // same command but with file lists not larger than about 2K int i = args.IndexOf(" -- ") + 3; string cmd = args.Substring(0, i + 1); args = args.Substring(i); // We separate git command up to and until the list of files App.PrintLogMessage("Processing large amount of files: please wait...", MessageType.General); // Add files individually up to the length limit using the starting " file delimiter string[] files = args.Split(new [] { " \"" }, StringSplitOptions.RemoveEmptyEntries); // Note: files in the list are now stripped from their initial " character! i = 0; do { StringBuilder batch = new StringBuilder(2100); while (batch.Length < 2000 && i < files.Length) { batch.Append("\"" + files[i++] + " "); } output = ClassGit.Run(cmd + batch, async); if (output.Success() == false) { break; } } while (i < files.Length); } catch (Exception ex) { App.PrintLogMessage(ex.Message, MessageType.Error); } return(output); }
/// <summary> /// Callback that handles process printing to stderr. /// Prints the stderr to a log window. /// </summary> private void PStderr(String message) { // Remove CSI [ or ESC [ + single character sequence if (message.StartsWith("\u001b[")) { message = message.Remove(0, 3); } // This is a workaround for Linux Mono: // On Windows, when we clone a remote repo, we receive each status line as a separate message // On Linux, it is all clumped together without any newlines (or 0A), so we inject them if (ClassUtils.IsMono()) { // A bit of a hack since we simply hard-code recognized types of messages. Oh, well... message = message.Replace("remote:", Environment.NewLine + "remote:"); message = message.Replace("Receiving", Environment.NewLine + "Receiving"); message = message.Replace("Resolving", Environment.NewLine + "Resolving"); } textStdout.AppendText(ClassUtils.ToPlainAscii(message) + Environment.NewLine, Color.Red); // Keep the newly added text visible textStdout.SelectionStart = textStdout.TextLength; textStdout.ScrollToCaret(); // This hack recognizes a common problem where the host RSA key was not added to the list // of known hosts. Help the user by telling him that and (on Windows) offering to open the // Manage Keys dialog. if (message.Contains("key fingerprint is")) { // On Linux / Mono, we don't have a Manage SSH dialog if (ClassUtils.IsMono()) { MessageBox.Show(@"The remote server RSA key was not added to the list of known hosts.", @"Host Key error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } else { DialogResult response = MessageBox.Show(@"The remote server RSA key was not added to the list of known hosts." + Environment.NewLine + @"Would you like to open the Manage SSH Keys dialog to add the host in the Remote Keys tab?", @"Host Key error", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); if (response == DialogResult.Yes) { FormSSH formSsh = new FormSSH(); formSsh.ShowDialog(); } } } // This hack recognizes a common HTTPS authentication error message if (message.Contains(@"fatal: Authentication failed for 'https:")) { MessageBox.Show(@"The remote server refused to authenticate you." + Environment.NewLine + @"You need to set your full HTTPS credentials (user name and password) to access this repo.", @"HTTPS Authentication error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } // Append the stderr stream message to a log window App.PrintLogMessage("stderr: " + message, MessageType.Error); }
/// <summary> /// Checks the path to git and checks that the git executable is functional. This call /// should be made only once upon the program start. /// It returns true if git executable can be run, false otherwise. /// </summary> public bool Initialize() { bool retValue = true; // Check that we have a functional version of git at an already set path gitPath = Properties.Settings.Default.GitPath; if (Exec.Run(gitPath, "--version").stdout.Contains("git version") == false) { // If we are running on Linux, get the git path by 'which' command if (ClassUtils.IsMono()) { gitPath = Exec.Run("which", "git").stdout.Trim(); if (Exec.Run(gitPath, "--version").stdout.Contains("git version") == false) { MessageBox.Show( "Could not locate 'git'!\n\nPlease install git by running 'sudo apt-get install git'\nMake sure it is on your path, then rerun this application.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); retValue = false; } } else { // Check if a version of git is installed at a known location (or guess a location) string programFilesPath = Environment.GetEnvironmentVariable("PROGRAMFILES(X86)") ?? Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); // If a git executable does not exist at the default location, try the 64-bit program file folder instead if (!File.Exists(programFilesPath) && programFilesPath.Contains(" (x86)")) { programFilesPath = programFilesPath.Replace(" (x86)", ""); } gitPath = Path.Combine(programFilesPath, @"Git\bin\git.exe"); if (Exec.Run(gitPath, "--version").stdout.Contains("git version") == false) { // Ask user to show us where the git is installed FormPathToGit formPath = new FormPathToGit(programFilesPath, gitPath); while (retValue = (formPath.ShowDialog() == DialogResult.OK)) { gitPath = formPath.PathToGit; if (Exec.Run(gitPath, "--version").stdout.Contains("git version")) { break; } } } } } if (retValue) { // Run the version again to get the version code (for simplicity did not save it earlier) string version = string.Format("Using {0} at {1}", Exec.Run(gitPath, "--version"), gitPath); App.PrintLogMessage(version, MessageType.General); Properties.Settings.Default.GitPath = gitPath; } return(retValue); }
/// <summary> /// Constructor class function, create batch file helper in the temp space /// </summary> public ClassSSH() { string pathHelpertLong = ClassUtils.WriteResourceToFile(Path.GetTempPath(), "git_ssh_helper.sh", Properties.Resources.git_ssh_helper); pathHelper = ClassUtils.GetShortPathName(pathHelpertLong); // Make the batch file executable: this trick will only work with Mono File.SetAttributes(pathHelper, (FileAttributes)((uint)File.GetAttributes(pathHelper) | 0x80000000)); App.PrintLogMessage("SSH helper path:" + pathHelper, MessageType.Error); ClassUtils.AddEnvar("GIT_SSH", pathHelper); }
/// <summary> /// Sets or removes a local git configuration value. /// If the value is null or empty string, the key will be removed (unset). /// </summary> public static void SetLocal(ClassRepo repo, string key, string value) { string setkey = string.IsNullOrEmpty(value) ? "--unset " : ""; string val = string.IsNullOrEmpty(value) ? "" : " \"" + value + "\""; string cmd = setkey + key + val; if (repo.Run("config --local " + cmd).Success() == false) { App.PrintLogMessage("Error setting local git config parameter!", MessageType.Error); } }
/// <summary> /// Open a file browser/Explorer at the specific directory, optionally selecting a file /// </summary> public static void ExplorerHere(string where, string selFile) { try { App.PrintStatusMessage("Opening a file browser at " + where, MessageType.General); if (IsMono()) { // Opening an "Explorer" is platform-specific and depends on the desktop environment which you use, // each desktop environment comes with its own default file- manager. // Ubuntu: nautilus // Cinnamon: nemo // Mate: caja // xfce: thunar // KDE: dolphin if (File.Exists(@"/usr/bin/nautilus")) { Process.Start(@"/usr/bin/nautilus", "--browser " + where); } else if (File.Exists(@"/usr/bin/nemo")) { Process.Start(@"/usr/bin/nemo", where); } else if (File.Exists(@"/usr/bin/caja")) { Process.Start(@"/usr/bin/caja", where); } else if (File.Exists(@"/usr/bin/thunar")) { Process.Start(@"/usr/bin/thunar", where); } else if (File.Exists(@"/usr/bin/dolphin")) { Process.Start(@"/usr/bin/dolphin", where); } else { throw new Exception("Unable to identify a file browser program for this distro. Please report this issue - thank you!"); } } else { string path = selFile == string.Empty ? "/e,\"" + where + "\"" : "/e, /select,\"" + selFile + "\""; App.PrintLogMessage(path, MessageType.Command); Process.Start("explorer.exe", path); } } catch (Exception ex) { App.PrintLogMessage(ex.Message, MessageType.Error); MessageBox.Show(ex.Message, "Explorer Here error", MessageBoxButtons.OK, MessageBoxIcon.Error); } }
/// <summary> /// Returns a value of a local git configuration key /// </summary> public static string GetLocal(ClassRepo repo, string key) { ExecResult result = repo.Run("config --local --get " + key); if (result.Success()) { return(result.stdout); } App.PrintLogMessage("Error getting local git config parameter!", MessageType.Error); return(string.Empty); }
/// <summary> /// Returns a value of a global git configuration key /// </summary> public static string GetGlobal(string key) { ExecResult result = ClassGit.Run("config --global --get " + key); if (result.Success()) { return(result.stdout); } App.PrintLogMessage("Error getting global git config parameter!", MessageType.Error); return(String.Empty); }
/// <summary> /// Initializes SSH connection by running the PLINK using the specified /// connection parameters. This function blocks until the PLINK returns. /// </summary> public void ImportRemoteSshKey(ClassUrl.Url url) { // Build the arguments to the PLINK process: port number, user and the host // Use the default SSH values if the url did not provide them string args = " -P " + (url.Port > 0 ? url.Port.ToString() : "22") + " -l " + (string.IsNullOrEmpty(url.User) ? "anonymous" : url.User) + " " + url.Host; // plink does everything through its stderr stream ExecResult result = Exec.Run(pathPlink, args); App.PrintLogMessage(result.stderr, MessageType.Error); }
/// <summary> /// Terminate this job /// </summary> public void Terminate() { try { FStdout = null; // Disable all callbacks since the client class could have been disposed of FStderr = null; FComplete = null; Proc.Kill(); // Immediately stop the process! } catch (Exception) { App.PrintLogMessage("Exec.Terminate() exception", MessageType.Error); } }
/// <summary> /// Deletes a folder from the local file system. /// Returns true if delete succeeded, false otherwise. /// </summary> private static bool DeleteFolder(DirectoryInfo dirInfo) { try { dirInfo.Attributes = FileAttributes.Normal; dirInfo.Delete(); } catch (Exception ex) { App.PrintLogMessage("Error deleting directory " + dirInfo.FullName + ": " + ex.Message, MessageType.Error); return(false); } return(true); }
/// <summary> /// Writes binary resource to a temporary file /// </summary> public static string WriteResourceToFile(string pathName, string fileName, byte[] buffer) { string path = Path.Combine(pathName, fileName); try { using (var sw = new BinaryWriter(File.Open(path, FileMode.Create))) { sw.Write(buffer); } } catch (Exception ex) { App.PrintLogMessage(ex.Message, MessageType.Error); } return(path); }
/// <summary> /// Deletes a file from the local file system. /// Returns true if delete succeeded, false otherwise. /// </summary> public static bool DeleteFile(string name) { try { FileInfo file = new FileInfo(name) { Attributes = FileAttributes.Normal }; file.Delete(); } catch (Exception ex) { App.PrintLogMessage("Error deleting file " + name + ": " + ex.Message, MessageType.Error); return(false); } return(true); }
/// <summary> /// Run plink program with the given arguments /// </summary> public void RunPLink(string args) { // Start a console process Process proc = new Process(); proc.StartInfo.FileName = ClassUtils.GetShellExecCmd(); proc.StartInfo.UseShellExecute = false; proc.StartInfo.CreateNoWindow = false; // We need to keep the CMD/SHELL window open, so start the process using // the CMD/SHELL as the root process and pass it our command to execute proc.StartInfo.Arguments = string.Format("{0} \"{1}\" {2}", ClassUtils.GetShellExecFlags(), pathPlink, args); App.PrintLogMessage(proc.StartInfo.Arguments, MessageType.Command); proc.Start(); }
/// <summary> /// The user selected an item from the list of help items to insert into /// the edit field. /// </summary> private void ComboHelpSelectedIndexChanged(object sender, EventArgs e) { try { // Insert (or substitute if there is a selection) first few characters // of a token into the corresponding edit field (kept in Tag of a combo box) ComboBox box = sender as ComboBox; box.Text = string.Empty; TextBox textBox = box.Tag as TextBox; string token = (string)box.SelectedItem; textBox.SelectedText = token.Substring(0, 2); textBox.Focus(); } catch (Exception ex) { App.PrintLogMessage("Error: " + ex.Message, MessageType.Error); } }
/// <summary> /// If user checked a preference to scan for TABS, and the file extension matches, /// check the file(s) for TABs and EOL's spaces /// </summary> public static void CheckForTabs(List <string> files) { // Only check if the user setting enables the functionality if (!Properties.Settings.Default.WarnOnTabs) { return; } // Contains the final list of files that have TABs or EOL spaces List <string> xfiles; // Wrap the file checks with a performance diagnostics so we can track how long it takes to parse all files Stopwatch timer = new Stopwatch(); timer.Start(); { // Create a Regex expression corresponding to each type of file extension to match string values = Properties.Settings.Default.WarnOnTabsExt; string[] extList = values.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); List <Regex> regexes = new List <Regex>(); foreach (string sFileMask in extList) { string expression = sFileMask.Trim().Replace(".", "[.]").Replace("*", ".*").Replace("?", ".") + "$"; regexes.Add(new Regex(expression)); } xfiles = CheckTabsInFiles(files, regexes); } timer.Stop(); App.PrintLogMessage("TabCheck: elapsed: " + timer.ElapsedMilliseconds + " ms", MessageType.Debug); // Print all files that have TABs or EOL spaces if (xfiles.Count > 0) { // Although it is a warning, internally we use a message type "Error" so the output will print in // red color and grab the attention App.PrintStatusMessage("WARNING: The following files contain TABs or EOL spaces:", MessageType.Error); foreach (string xfile in xfiles) { App.PrintStatusMessage(xfile, MessageType.Error); } } }
/// <summary> /// Main command execution function. /// Upon completion, prints all errors to the log window. /// </summary> public static ExecResult Run(string cmd, string args) { App.PrintLogMessage(String.Format("Exec: {0} {1}", cmd, args), MessageType.Command); App.StatusBusy(true); Exec job = new Exec(cmd, args); job.Thread = new Thread(job.ThreadedRun); job.Thread.Start(10000); job.Thread.Join(); // There are known problems with async output not being flushed as the // thread exits. Releasing a time-slice using DoEvents seems to fix // the problem in this particular setting. Application.DoEvents(); App.StatusBusy(false); if (job.Result.Success() == false) { App.PrintLogMessage("Error: " + job.Result.stderr, MessageType.Error); } return(job.Result); }
/// <summary> /// Check a list of files, filtered by a list of Regex expressions, for TABs or EOL spaces /// Returns a subset of files that contain TABs or EOL spaces /// </summary> private static List <string> CheckTabsInFiles(List <string> files, List <Regex> regexes) { List <string> xfiles = new List <string>(); // Filter which files to check by using a regular expression of each file name foreach (string file in files) { foreach (Regex regex in regexes) { // This file is to be checked using this particular regular expression if (regex.IsMatch(file)) { App.PrintLogMessage("TabCheck: " + file, MessageType.Debug); if (CheckTabsInFile(file)) { xfiles.Add(file); } } } } return(xfiles); }
/// <summary> /// Edit selected file using either the default editor (native OS file association, /// if the tag is null, or the editor program specified in the tag field. /// This is a handler for the context menu, edit tool bar button and also /// revision history view menus. /// </summary> public static void FileOpenFromMenu(object sender, string file) { try { if (sender is ToolStripMenuItem) { object opt = (sender as ToolStripMenuItem).Tag; if (opt != null) { App.PrintLogMessage("Exec: " + opt.ToString() + " " + file, MessageType.General); Process.Start(opt.ToString(), file); return; } } App.PrintLogMessage("Exec: " + file, MessageType.General); Process.Start(file); } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } }
/// <summary> /// Load or merge a set of repositories from a file. /// Returns true is loading succeeded and this class is assigned a new set of repos. /// Returns false if loading failed, or was cancelled. The repos in this class did not change. /// If the requested operation is a merge, isImport=true, the new set of repos will be merged with the existing one. /// </summary> public bool Load(string fileName, bool isImport) { // Wrap the opening of a repository database with an outer handler try { using (FileStream file = new FileStream(fileName, FileMode.Open)) { try { // Load list of repos and the default repo string into temporary objects BinaryFormatter rd = new BinaryFormatter(); List <ClassRepo> newRepos = (List <ClassRepo>)rd.Deserialize(file); string defaultRepo = (string)rd.Deserialize(file); // WAR: Mono 2.6.7 does not support serialization of a HashSet. At the same time... // Quickly check that each repo is valid (find if at least one is not) bool allOK = true; foreach (ClassRepo repo in newRepos) { allOK &= ClassUtils.DirStat(repo.Path) == ClassUtils.DirStatType.Git; repo.ExpansionReset(null); } // If at least one repo was not valid, give the user a chance to recreate it if (allOK == false) { FormRecreateRepos recreateRepos = new FormRecreateRepos(); recreateRepos.Repos = newRepos; // The "Accept" button in the recreate repos dialog will not be enabled // until every repo has been configured properly. User can always cancel // that process in which case we will not load the selected repo. if (recreateRepos.ShowDialog() == DialogResult.Cancel) { return(false); } newRepos = recreateRepos.Repos; } // If the operation is a simple load, assign our object's list of repos // Otherwise, we merge the new set with the existing one if (isImport) { Repos.AddRange(newRepos); } // After we merge the new set of repos, current/default repo remains the same else { Repos = newRepos; // Upon load, set the current based on the default repo Default = Repos.Find(r => r.Path == defaultRepo); SetCurrent(Default); } return(true); } catch (Exception ex) { throw new ClassException(ex.Message); } } } catch (Exception ex) { App.PrintLogMessage(ex.Message, MessageType.Error); } return(false); }
/// <summary> /// Runs a custom tool. /// Returns a string with a tool output to be printed out. /// This string can be empty, in which case nothing should be printed. /// </summary> public string Run(List <string> files) { App.PrintLogMessage(ToString(), MessageType.Command); string stdout = string.Empty; string args = DeMacroise(Args, files); // Add custom arguments if the checkbox to Prompt for Arguments was checked if (IsPromptForArgs) { // Description is used as a question for the arguments, shown in the window title bar string desc = Name; if (!string.IsNullOrEmpty(Desc)) { desc += ": " + Desc; } FormCustomToolArgs formCustomToolArgs = new FormCustomToolArgs(desc, args, IsAddBrowse); if (formCustomToolArgs.ShowDialog() == DialogResult.Cancel) { return(string.Empty); } args = formCustomToolArgs.GetArgs(); } App.StatusBusy(true); // Prepare the process to be run Process proc = new Process(); proc.StartInfo.FileName = "\"" + Cmd + "\""; proc.StartInfo.Arguments = args; proc.StartInfo.WorkingDirectory = DeMacroise(Dir, new List <string>()); proc.StartInfo.UseShellExecute = false; try { // Run the custom tool in two ways (console app and GUI app) if (IsConsoleApp) { // Start a console process proc.StartInfo.CreateNoWindow = false; // If we have to keep the window open (CMD/SHELL) after exit, // we start the command line app in a different way, using a // shell command (in which case we cannot redirect the stdout) if (IsCloseWindowOnExit) { App.MainForm.SetTitle("Waiting for " + Cmd + " to finish..."); // Redirect standard output to our status pane if requested if (IsWriteOutput) { proc.StartInfo.RedirectStandardOutput = true; proc.StartInfo.RedirectStandardError = true; proc.OutputDataReceived += ProcOutputDataReceived; proc.ErrorDataReceived += ProcErrorDataReceived; proc.Start(); proc.BeginOutputReadLine(); proc.WaitForExit(); } else { proc.Start(); proc.WaitForExit(); } } else { // We need to keep the CMD/SHELL window open, so start the process using // the CMD/SHELL as the root process and pass it our command to execute proc.StartInfo.Arguments = string.Format("{0} {1} {2}", ClassUtils.GetShellExecFlags(), proc.StartInfo.FileName, proc.StartInfo.Arguments); proc.StartInfo.FileName = ClassUtils.GetShellExecCmd(); App.PrintLogMessage(proc.StartInfo.Arguments, MessageType.Command); proc.Start(); } } else { // Start a GUI process proc.StartInfo.CreateNoWindow = true; // We can start the process and wait for it to finish only if we need to // refresh the app after the process has exited. proc.Start(); } if (IsRefresh) { App.MainForm.SetTitle("Waiting for " + Cmd + " to finish..."); proc.WaitForExit(); App.DoRefresh(); } } catch (Exception ex) { App.PrintStatusMessage(ex.Message, MessageType.Error); MessageBox.Show(ex.Message, "Error executing custom tool", MessageBoxButtons.OK, MessageBoxIcon.Error); } proc.Close(); App.StatusBusy(false); return(stdout); }