/// <summary> /// Create a git file of a specific version. Use a temp file since the file /// content needs to be created from the git history. /// </summary> private string GetTempFile(string file, string sha) { // git show commands needs '/' as file path separator string gitpath = file.Replace(Path.DirectorySeparatorChar, '/'); string cmd = string.Format("show {1}:\"{0}\"", gitpath, sha); ExecResult result = App.Repos.Current.Run(cmd); if (result.Success() == false) { return(string.Empty); } string response = result.stdout; // Create a temp file based on a version of our file and write its content to it string rev = listRev.Items.Find(sha, false)[0].Text.Trim(); file = Path.GetFileName(file); file = string.Format("ReadOnly-{0}-Rev-{1}-{2}", tmpFileCounter, rev, file); tmpFileCounter++; string tempFile = Path.Combine(Path.GetTempPath(), file); try { File.WriteAllText(tempFile, response); } catch (Exception ex) { MessageBox.Show(ex.Message, "System error", MessageBoxButtons.OK, MessageBoxIcon.Error); } // Add the temp file to the global list of temp files to be removed at the app exit time ClassGlobals.TempFiles.Add(tempFile); return(tempFile); }
/// <summary> /// User clicked on a log item. Fetch its full description. /// </summary> private void ListRevSelectedIndexChanged(object sender, EventArgs e) { // Set the menu enables according to the number of items selected viewMenuItem.Enabled = syncMenuItem.Enabled = diffVsClientFileMenuItem.Enabled = listRev.SelectedIndices.Count == 1; diffRevisionsMenuItem.Enabled = listRev.SelectedIndices.Count == 2; // Set up for 2 SHA checkins: the one in the [0] spot being the most recently selected if (listRev.SelectedIndices.Count == 1) { lruSha[0] = lruSha[1] = listRev.SelectedItems[0].Name; } if (listRev.SelectedIndices.Count > 1) { if (listRev.SelectedItems[0].Name == lruSha[0]) { lruSha[1] = listRev.SelectedItems[1].Name; } else { lruSha[1] = listRev.SelectedItems[0].Name; } } // Fill in the description of a selected checkin if a single one is selected if (listRev.SelectedIndices.Count == 1) { string sha = lruSha[1]; string cmd = string.Format("show -s {0}", sha); ExecResult result = App.Repos.Current.Run(cmd); textDescription.Text = result.Success() ? result.stdout : result.stderr; } }
/// <summary> /// Reload status fields in the context of a status class /// </summary> private void Status() { XY.Clear(); AltFile.Clear(); ExecResult result = Repo.Run("status --porcelain -uall -z"); if (!result.Success()) { return; } string[] response = result.stdout .Replace('/', Path.DirectorySeparatorChar) // Correct the path slash on Windows .Split(("\0") .ToCharArray(), StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < response.Length; i++) { string s = response[i].Substring(3); string code = response[i].Substring(0, 2); XY[s] = code; if ("RC".Contains(response[i][0])) { i++; AltFile[s] = response[i]; } } }
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> /// Refresh the list of remotes for the given repo while keeping the /// existing passwords and push commands /// </summary> public void Refresh(ClassRepo repo) { // Build the new list while picking up password fields from the existing list Dictionary <string, Remote> newlist = new Dictionary <string, Remote>(); string[] response = new[] { string.Empty }; ExecResult result = repo.Run("remote -v"); if (result.Success()) { response = result.stdout.Split((Environment.NewLine).ToCharArray(), StringSplitOptions.RemoveEmptyEntries); foreach (string s in response) { Remote r = new Remote(); // Split the resulting remote repo name/url into separate strings string[] url = s.Split("\t ".ToCharArray()); string name = url[0]; // Find if the name exists in the main list and save off the password from it if (newlist.ContainsKey(name)) { r = newlist[name]; } if (remotes.ContainsKey(name)) { r.Password = remotes[name].Password; r.PushCmd = remotes[name].PushCmd; } // Set all other fields that we refresh every time r.Name = name; if (url[2] == "(fetch)") { r.UrlFetch = url[1]; } if (url[2] == "(push)") { r.UrlPush = url[1]; } // Add it to the new list newlist[name] = r; } } // Set the newly built list to be the master list remotes = newlist; // Fixup the new current string name if (!remotes.ContainsKey(Current)) { Current = remotes.Count > 0 ? remotes.ElementAt(0).Key : ""; } }
/// <summary> /// Switch to a named branch /// </summary> public bool SwitchTo(string name) { // Make sure the given branch name is a valid local branch if (!string.IsNullOrEmpty(name) && Local.IndexOf(name) >= 0) { ExecResult result = App.Repos.Current.RunCmd("checkout " + name); return(result.Success()); } return(false); }
/// <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> /// 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> /// 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> /// Sync file to the selected revision /// </summary> private void SyncMenuItemClick(object sender, EventArgs e) { if (MessageBox.Show("This will sync file to a previous version. Continue?", "Revision Sync", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { string cmd = string.Format("checkout {1} -- {0}", file, lruSha[0]); ExecResult result = App.Repos.Current.RunCmd(cmd); if (result.Success()) { App.PrintStatusMessage("File checked out at a previous revision " + lruSha[0] + ": " + file, MessageType.General); App.DoRefresh(); } else { App.PrintStatusMessage("Sync error: " + result.stderr, MessageType.Error); MessageBox.Show(result.stderr, "Sync error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } }
/// <summary> /// User clicked on the Install button.. We need to find the latest msysgit build, /// download it and run it /// </summary> private void BtInstallClick(object sender, EventArgs e) { string installerFile = Path.GetTempFileName(); // Junk name so we can safely call 'Delete' try { // msysgit is hosted at https://github.com/msysgit/msysgit/releases // and the files can be downloaded at the subfolder 'download': FormDownload msysgit = new FormDownload("Download msysgit", @"https://github.com/msysgit/msysgit/releases", @"(?<file>Git-[1-2]+.[0-9]+.[0-9]+-\S+.exe)", "/download/"); // If the download succeeded, run the installer file if (msysgit.ShowDialog() == DialogResult.OK) { installerFile = msysgit.TargetFile; ExecResult ret = Exec.Run(installerFile, String.Empty); if (ret.retcode == 0) { // Check if the git executable is at the expected location now if (File.Exists(gitPath)) { PathToGit = textBoxPath.Text = gitPath; } } DialogResult = DialogResult.OK; } } catch (Exception ex) { MessageBox.Show(ex.Message, "Error"); } finally { // Make sure we don't leave temporary files around File.Delete(installerFile); } }
/// <summary> /// The form is loading. Get the file log information and fill it in. /// </summary> private void FormRevisionHistoryLoad(object sender, EventArgs e) { // Get the list of revisions by running a git command StringBuilder cmd = new StringBuilder("log "); cmd.Append(" --pretty=format:"); // Start formatting section cmd.Append("%h%x09"); // Abbreviated commit hash cmd.Append("%ct%x09"); // Committing time, UNIX-style cmd.Append("%an%x09"); // Author name cmd.Append("%s"); // Subject // Limit the number of commits to show if (Properties.Settings.Default.commitsRetrieveAll == false) { cmd.Append(" -" + Properties.Settings.Default.commitsRetrieveLast); } // Get the log of a single file only cmd.Append(" -- \"" + file + "\""); ExecResult result = App.Repos.Current.Run(cmd.ToString()); if (result.Success()) { PanelRevlist.UpdateList(listRev, result.stdout, true, string.Empty); } if (listRev.Items.Count > 0) { // Activate the given SHA item or the first one if none given int index = listRev.Items.IndexOfKey(Sha); if (index < 0) { index = 0; } listRev.SelectedIndices.Add(index); listRev.Items[index].EnsureVisible(); } }
/// <summary> /// Callback that handles process completion event /// </summary> private void PComplete(ExecResult result) { UseWaitCursor = false; this.result = result; if (result.Success()) { toolStripStatus.Text = "Git command completed successfully."; textStdout.AppendText("Git command completed successfully.", Color.Green); // On success, auto-close the dialog if the user's preference was checked // This behavior can be skipped if the user holds down the Control key if (Properties.Settings.Default.AutoCloseGitOnSuccess && Control.ModifierKeys != Keys.Control) { DialogResult = DialogResult.OK; } } else { toolStripStatus.Text = "Git command failed!"; textStdout.AppendText("Git command failed!", Color.Red); } btCancel.Text = "Done"; StopProgress(); }
/// <summary> /// Add new remote repository /// </summary> private void BtAddClick(object sender, EventArgs e) { ClassRemotes.Remote remote = new ClassRemotes.Remote(); FormRemoteAddEdit remoteAddEdit = new FormRemoteAddEdit(); remoteAddEdit.Prepare(FormRemoteAddEdit.Function.Add, remote); if (remoteAddEdit.ShowDialog() == DialogResult.OK) { remote = remoteAddEdit.Get(); ExecResult result = _repo.Run("remote add " + remote.Name + " " + remote.UrlFetch); if (result.Success()) { _repo.Remotes.SetPassword(remote.Name, remote.Password); _repo.Remotes.SetPushCmd(remote.Name, remote.PushCmd); SetRepo(_repo); } else { MessageBox.Show("Git is unable to add this remote repository.", "Add remote repository", MessageBoxButtons.OK, MessageBoxIcon.Error); App.PrintStatusMessage(result.stderr, MessageType.Error); } } }
public ExecResult RunCmd(string args, bool async) { // Print the actual command line to the status window only if user selected that setting if (Properties.Settings.Default.logCommands) { App.PrintStatusMessage("git " + args, MessageType.Command); } // Run the command and print the response to the status window in any case ExecResult result = Run(args, async); if (result.stdout.Length > 0) { App.PrintStatusMessage(result.stdout, MessageType.Output); } // If the command caused an error, print it also if (result.Success() == false) { App.PrintStatusMessage(result.stderr, MessageType.Error); } return(result); }
/// <summary> /// Populate list with existing stashes /// </summary> private void PopulateStashList() { listStashes.Items.Clear(); ExecResult result = App.Repos.Current.Run("stash list"); if (result.Success()) { string[] response = result.stdout.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); foreach (var stash in response) { listStashes.Items.Add(stash); } } // Disable buttons (in the case the stash list was empty) // but re-enable them once we have a stash to select (select the top one by default) btApply.Enabled = btRemove.Enabled = btShow.Enabled = false; if (listStashes.Items.Count > 0) { string s = listStashes.Items[0].ToString(); stash = s.Substring(0, s.IndexOf(':')); listStashes.SelectedIndex = 0; btApply.Enabled = btRemove.Enabled = btShow.Enabled = true; } }
/// <summary> /// Button clicked to delete a selected branch. /// This method is called only if a branch has been selected (otherwise the Delete button was disabled) /// </summary> private void DeleteClick(object sender, EventArgs e) { string cmd; string selectedBranch = listBranches.SelectedItem.ToString(); // Depending on the branch (local or remote), use a different way to delete it if (radioLocalBranch.Checked) { cmd = String.Format("branch {0} {1}", checkForce.Checked ? "-D" : "-d", selectedBranch); } else { // Remote branch string repoName = selectedBranch.Split('/')[0]; string branchName = selectedBranch.Split('/')[1]; if (checkTracking.Checked) { cmd = String.Format("branch -rd {0}", branchName); // Remove a reference to a remote branch } else { cmd = String.Format("push {0} :{1}", repoName, branchName); } } // Execute the final branch command and if fail, show the dialog box asking to retry ExecResult result = App.Repos.Current.RunCmd(cmd); if (result.Success() == false) { if (MessageBox.Show(result.stderr, "Error deleting branch", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error) == DialogResult.Retry) { DialogResult = DialogResult.None; } } }
/// <summary> /// Called on a change of radio button selection for the branch origin /// </summary> private void RadioBranchSourceCheckedChanged(object sender, EventArgs e) { RadioButton rb = sender as RadioButton; if (rb.Checked == false) { switch (rb.Tag.ToString()) { case "SHA1": textSHA1.Enabled = false; break; default: listBranches.Enabled = false; listBranches.BackColor = SystemColors.Control; break; } origin = null; } else { switch (rb.Tag.ToString()) { case "Head": break; case "SHA1": textSHA1.Enabled = true; break; case "Local": listBranches.Items.Clear(); foreach (var branch in branches.Local) { listBranches.Items.Add(branch); } listBranches.Enabled = true; listBranches.BackColor = SystemColors.Window; break; case "Remote": listBranches.Items.Clear(); foreach (var branch in branches.Remote) { listBranches.Items.Add(branch); } listBranches.Enabled = true; listBranches.BackColor = SystemColors.Window; break; case "Tag": listBranches.Items.Clear(); ExecResult result = App.Repos.Current.Run("tag"); if (result.Success()) { string[] tags = result.stdout.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); foreach (var tag in tags) { listBranches.Items.Add(tag); } listBranches.Enabled = true; } listBranches.BackColor = SystemColors.Window; break; default: break; } } }
/// <summary> /// Callback that handles process completion event /// </summary> private void PComplete(ExecResult result) { _result = result; if (result.Success()) { toolStripStatus.Text = "Git command completed successfully."; textStdout.AppendText("Git command completed successfully.", Color.Green); } else { toolStripStatus.Text = "Git command failed!"; textStdout.AppendText("Git command failed!", Color.Red); } btCancel.Text = "Done"; StopProgress(); }
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); // Add files individually up to the length limit string[] files = args.Split(' '); 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); } return output; }
/// <summary> /// Given a Sha string, loads that commit into the form. /// </summary> public void LoadChangelist(string sha) { Tag = sha; // Store the SHA of a current commit in the Tag field of this form // Issuing "show" command can take _very_ long time with a commit full of files // Run a much faster 'whatchanged' command first to get the list of files string cmd = "whatchanged " + sha + " -n 1 --format=medium"; ExecResult result = App.Repos.Current.Run(cmd); string[] response = new[] { string.Empty }; if (result.Success()) { response = result.stdout.Split(new[] { cr }, StringSplitOptions.None); } // Go over the resulting list and add to our text box textChangelist.Text = ""; // Clear the rich text box // Get the list of lines that describe individual files vs the rest (checkin comment) List <string> files = response.Where(s => s.StartsWith(":")).ToList(); List <string> comment = response.Where(s => !s.StartsWith(":")).ToList(); // ---------------- Print the comment section ---------------- foreach (string s in comment) { textChangelist.AppendText(s + cr); } // ---------------- Print the files section ---------------- textChangelist.AppendText(cr + "Files:" + cr + cr, Color.Red); foreach (string s in files) { // Parse the file name indicator following this template: // :100644 000000 ed81075... 0000000... D file0 // [attributes] [prev] [next] [file] // 0 1 2 3 4 (preamble[]) // Starting with git 2.10, new output is added for copy-edit and rename-edit (https://git-scm.com/docs/git-diff-tree) // :100644 000000 ed81075... 0000000... C68 file1 file2 (copy-edit with a similarity "score") // ^tab ^tab string[] parts = s.Split(new[] { '\t' }, StringSplitOptions.RemoveEmptyEntries); string[] preamble = parts[0].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if ((parts.Length == 2 || parts.Length == 3) && preamble.Length == 5) { // In the tag of the link, we will send file name and related SHA keys (stripped of ".") string tag = string.Format("{0}#{1}#{2}", parts[1], preamble[2].Replace(".", ""), preamble[3].Replace(".", "")); textChangelist.AppendText(s[37] + "\t"); if (parts.Length == 3) // Two files are listed { textChangelist.AppendText(parts[1] + " -> "); textChangelist.InsertLink(parts[2], tag); } else { textChangelist.InsertLink(parts[1], tag); } } else { textChangelist.AppendText(s); } textChangelist.AppendText(cr); } // Now optionally run the detailed show command, but if the number of files is large, // ask the user to confirm for any more than, say, 30 files if (comboShow.SelectedIndex == 0) { return; } if (files.Count > 30) { string q = "The number of files changed in this commit is very large and it may take considerable time" + " to display their detailed difference. Do you still want to proceed?\n\n(To skip this message in the future, select " + "(none) in the details option)"; if (MessageBox.Show(q, "Detailed difference of " + files.Count + " files", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.No) { return; } } cmd = "show -t " + sha + " --format=" + comboShow.SelectedItem; result = App.Repos.Current.Run(cmd); if (result.Success()) { response = result.stdout.Split(new[] { cr }, StringSplitOptions.None); } textChangelist.AppendText(cr + "Details" + cr + cr, Color.Red); // Write out the complete response text containing all files' differences foreach (string s in response) { textChangelist.SelectedText = s + cr; } textChangelist.Select(0, 0); }
/// <summary> /// Callback that handles process completion event /// </summary> private void PComplete(ExecResult result) { textStdout.Cursor = Cursors.IBeam; // Default cursor for the text box this.result = result; if (result.Success()) { toolStripStatus.Text = "Git command completed successfully."; textStdout.AppendText("Git command completed successfully.", Color.Green); // On success, auto-close the dialog if the user's preference was checked // This behavior can be skipped if the user holds down the Control key if (Properties.Settings.Default.AutoCloseGitOnSuccess && Control.ModifierKeys != Keys.Control) DialogResult = DialogResult.OK; } else { toolStripStatus.Text = "Git command failed!"; textStdout.AppendText("Git command failed!", Color.Red); } btCancel.Text = "Done"; StopProgress(); }
/// <summary> /// Given a Sha string, loads that commit into the form. /// </summary> public void LoadChangelist(string sha) { Tag = sha; // Store the SHA of a current commit in the Tag field of this form // Issuing "show" command can take _very_ long time with a commit full of files // Run a much faster 'whatchanged' command first to get the list of files string cmd = "whatchanged " + sha + " -n 1 --format=medium"; ExecResult result = App.Repos.Current.Run(cmd); string[] response = new[] { string.Empty }; if (result.Success()) { response = result.stdout.Split(new[] { cr }, StringSplitOptions.None); } // Go over the resulting list and add to our text box textChangelist.Text = ""; // Clear the rich text box // Get the list of lines that describe individual files vs the rest (checkin comment) List <string> files = response.Where(s => s.StartsWith(":")).ToList(); List <string> comment = response.Where(s => !s.StartsWith(":")).ToList(); // ---------------- Print the comment section ---------------- foreach (string s in comment) { textChangelist.AppendText(s + cr); } // ---------------- Print the files section ---------------- textChangelist.AppendText(cr + "Files:" + cr + cr, Color.Red); foreach (string s in files) { // Parse the file name indicator following this template: // :100644 000000 ed81075... 0000000... D Build/Help.SED // [attributes] [prev] [next] [file] (meaning) // 0 1 2 3 4 5 (chunk) // We strip tabs and '.' characters which are part of SHA keys in the line string[] chunk = s.Replace('.', ' ').Split(new[] { ' ', '\t', '.' }, StringSplitOptions.RemoveEmptyEntries); if (s.Length >= 39) // Hard-coded value! Depends on the git output. { // In the tag of the link, we will send file name and related SHA keys string tag = string.Format("{0}#{1}#{2}", s.Substring(39), chunk[2], chunk[3]); textChangelist.AppendText(s.Substring(37, 2)); textChangelist.InsertLink(s.Substring(39), tag); textChangelist.AppendText(cr); } else { textChangelist.AppendText(s + cr); } } // Now optionally run the detailed show command, but if the number of files is large, // ask the user to confirm for any more than, say, 30 files if (comboShow.SelectedIndex == 0) { return; } if (files.Count > 30) { string q = "The number of files changed in this commit is very large and it may take considerable time" + " to display their detailed difference. Do you still want to proceed?\n\n(To skip this message in the future, select " + "(none) in the details option)"; if (MessageBox.Show(q, "Detailed difference of " + files.Count + " files", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.No) { return; } } cmd = "show -t " + sha + " --format=" + comboShow.SelectedItem; result = App.Repos.Current.Run(cmd); if (result.Success()) { response = result.stdout.Split(new[] { cr }, StringSplitOptions.None); } textChangelist.AppendText(cr + "Details" + cr + cr, Color.Red); // Write out the complete response text containing all files' differences foreach (string s in response) { textChangelist.SelectedText = s + cr; } textChangelist.Select(0, 0); }