/// <summary> /// Build a tree view of commit nodes using hints from repo commit groups. /// Commit nodes have Tags containing absolute paths to files, /// while the relative paths (with respect to repo root) are simply in the Text. /// </summary> public static TreeNode BuildCommitsView(ClassRepo repo, List <string> list) { repo.Commits.Rebuild(list); // Step 1: Build the default list with all files under this status class TreeNode root = new TreeNode("Pending Changelists"); root.Tag = "root"; // Tag of the root node foreach (var c in repo.Commits.Bundle) { var commitNode = new TreeNode(GetCommitNodeText(c)) { Tag = c }; if (c.IsCollapsed) { commitNode.Collapse(); } foreach (var f in c.Files) { var tn = new TreeNode(f) { Tag = f }; commitNode.Nodes.Add(tn); } root.Nodes.Add(commitNode); } root.ExpandAll(); return(root); }
public FormRemoteEdit(ClassRepo repo) { InitializeComponent(); ClassWinGeometry.Restore(this); userControlRemoteEdit.SetRepo(repo); }
/// <summary> /// Add a new repository with the root at the given path. Create a directory if it does not exist. /// This function throws exceptions! /// </summary> /// <param name="root">The root path of a new repository</param> /// <returns>Newly created repository class or null if a repo already exists at that root directory</returns> public ClassRepo Add(string root) { // Detect a repository with the same root path (case insensitive directory name compare) if (Repos.Exists(r => r.Path.Equals(root, StringComparison.CurrentCultureIgnoreCase))) { throw new ClassException("Repository with the same name already exists!"); } Directory.CreateDirectory(root); ClassRepo repo = new ClassRepo(root); if (!repo.Init()) { throw new ClassException("Unable to initialize git repository!"); } Repos.Add(repo); App.PrintStatusMessage("Adding repo " + repo, MessageType.General); // If this is a very first repo, set it as default and current if (Repos.Count == 1) { Current = Default = repo; } return(repo); }
public FormRemoteEdit(ClassRepo repo) { InitializeComponent(); ClassWinGeometry.Restore(this); userControlRemotesEdit.SetRepo(repo); }
/// <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> /// 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> /// 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> /// Global status refresh function refreshes status of the current repo. /// </summary> public static void Refresh() { ClassRepo repo = App.Repos.Current; if (repo != null) { repo.Status = new ClassStatus(repo); } }
/// <summary> /// Set the new current repo. /// Given repo can be null in which case the first repo will be selected as current. /// </summary> public void SetCurrent(ClassRepo repo) { // Assign the new 'current' repo but if null, try to find the first valid repo Current = (repo == null && Repos.Count > 0)? Repos[0] : repo; if (Current != null) { Current.Remotes.Refresh(Current); // Refresh the list of remote repos } }
public void SetRepo(ClassRepo repo) { _repo = repo; ListRefresh(); // Select the first item, if there is any if (listRemotes.Items.Count > 0) listRemotes.SelectedIndex = 0; }
/// <summary> /// Implement default comparator so these classes can be sorted by their (root) name /// </summary> public int CompareTo(object obj) { if (obj == null) { return(1); } ClassRepo a = obj as ClassRepo; return(a != null?ToString().CompareTo(a.ToString()) : 1); }
/// <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); } }
public void SetRepo(ClassRepo repo) { _repo = repo; ListRefresh(); // Select the first item, if there is any if (listRemotes.Items.Count > 0) { listRemotes.SelectedIndex = 0; } }
/// <summary> /// User clicked on the Locate button /// Open the directory finder and accept a new repo root. This method is called only /// when a single repo is being selected and the root of that repo will be changed. /// </summary> private void BtLocateClick(object sender, EventArgs e) { folder.Description = @"Select a folder where this git repository is located:"; folder.ShowNewFolderButton = false; if (folder.ShowDialog() == DialogResult.OK) { // We are sure that one and only one repo will be selected ClassRepo repo = list.SelectedItems[0].Tag as ClassRepo; repo.Path = folder.SelectedPath; RefreshView(); } }
/// <summary> /// Loads a file or a set of files and prepares the dialog controls /// Two different modes of opeation: single file or multiple files /// </summary> public bool LoadFiles(ClassRepo targetRepo, string[] files) { repo = targetRepo; // Load the original list of files into the text box to show what will be renamed // Only load files, not directories foreach (string file in files.Where(file => !file.EndsWith(Convert.ToString(Path.DirectorySeparatorChar)))) inFiles.Add(file); if (inFiles.Count == 0) return false; // Show files as relative to the repo root textOriginalNames.Clear(); foreach (string file in inFiles) textOriginalNames.Text += @"//" + file + Environment.NewLine; // Set the New Name(s) accordingly if (inFiles.Count == 1) { // Single file - initial proposed new name is the same textNewName.Text = inFiles[0]; } else { // Multiple files - proposed new name (filespec) is a common directory path int i; bool fDone = false; // Iteratively find the common path prefix for (i = 0; !fDone; i++) { char c = inFiles[0][i]; foreach (string file in inFiles) if (file.Length == i || file[i] != c) fDone = true; } i = inFiles[0].Substring(0, i).LastIndexOf('/'); if (i > 0) { multiFileCommonPath = inFiles[0].Substring(0, i); textNewName.Text = multiFileCommonPath + @"/..."; } } // Set the changelist options comboChangelist.Items.Clear(); foreach (ClassCommit bundle in repo.Commits.Bundle) comboChangelist.Items.Add(bundle); comboChangelist.Items.Add("New"); comboChangelist.SelectedIndex = 0; return true; }
/// <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> /// Delete a repository given by its class variable /// </summary> public void Delete(ClassRepo repo) { Repos.Remove(repo); // If the current has been deleted, find a new current if (repo == Current) SetCurrent(Repos.Count > 0 ? Repos[0] : null); // Reset the default if that one has been deleted if (repo == Default) Default = Current; }
/// <summary> /// Delete a repository given by its class variable /// </summary> public void Delete(ClassRepo repo) { Repos.Remove(repo); // If the current has been deleted, find a new current if (repo == Current) { SetCurrent(Repos.Count > 0 ? Repos[0] : null); } // Reset the default if that one has been deleted if (repo == Default) { Default = Current; } }
/// <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> /// User clicked on the Browse / common path button /// Change common parts of the repo paths for all selected repos. Open the directory finder /// and let the user select a new folder to use as a common root path /// </summary> private void BtBrowseClick(object sender, EventArgs e) { folder.Description = @"Select a new root path to substitute the common part of the paths of all selected repos:"; folder.ShowNewFolderButton = true; if (folder.ShowDialog() == DialogResult.OK) { // We are sure that 2 or more repos will be selected foreach (int index in list.SelectedIndices) { ClassRepo repo = list.Items[index].Tag as ClassRepo; repo.Path = folder.SelectedPath + repo.Path.Substring(textRootPath.Text.Length); RefreshView(); } // Disable Browse button so the user can't immediately repeat the operation since we // just changed all paths and would need a selection changed event to rebuild the textRootPath btBrowse.Enabled = false; } }
/// <summary> /// User clicked on the Create button /// Run the new repo wizard to create/clone/pull that missing repo /// </summary> private void BtCreateClick(object sender, EventArgs e) { // We are sure that one and only one repo will be selected ClassRepo repo = list.SelectedItems[0].Tag as ClassRepo; string root = PanelRepos.NewRepoWizard(null, repo, null); if (!string.IsNullOrEmpty(root)) { repo.Path = root; FormRepoEdit repoEdit = new FormRepoEdit(repo); repoEdit.ShowDialog(); // Modify our list of repos to show the new path list.SelectedItems[0].Tag = repo; RefreshView(); } }
public FormRepoEdit(ClassRepo targetRepo) { InitializeComponent(); ClassWinGeometry.Restore(this); repo = targetRepo; // Add all user panels to the base repo edit panel; call their init foreach (KeyValuePair <string, UserControl> key in panels) { panel.Controls.Add(key.Value); (key.Value as IRepoSettings).Init(repo); key.Value.Dock = DockStyle.Fill; } // Expand the tree and select the first node treeSections.ExpandAll(); treeSections.SelectedNode = treeSections.Nodes[0].Nodes[0]; }
/// <summary> /// Add a new repository with the root at the given path. Create a directory if it does not exist. /// This function throws exceptions! /// </summary> /// <param name="root">The root path of a new repository</param> /// <returns>Newly created repository class or null if a repo already exists at that root directory</returns> public ClassRepo Add(string root) { // Detect a repository with the same root path if (Repos.Exists(r => r.Root == root)) throw new ClassException("Repository with the same name already exists!"); Directory.CreateDirectory(root); ClassRepo repo = new ClassRepo(root); if (!repo.Init()) throw new ClassException("Unable to initialize git repository!"); Repos.Add(repo); App.PrintStatusMessage("Adding repo " + repo, MessageType.General); // If this is a very first repo, set it as default and current if (Repos.Count == 1) Current = Default = repo; return repo; }
public FormRepoEdit(ClassRepo targetRepo) { InitializeComponent(); ClassWinGeometry.Restore(this); repo = targetRepo; // Get all local configuration strings and assign various panel controls. // This is placed first, before initializing the user panels, so that the // strings are accessible to individual panels should they need to use them. // TODO: This is not needed for now //string[] config = _repo.Run("config --local --list -z").Split('\0'); // Add all user panels to the base repo edit panel; call their init foreach (KeyValuePair<string, UserControl> key in panels) { panel.Controls.Add(key.Value); (key.Value as IRepoSettings).Init(repo); key.Value.Dock = DockStyle.Fill; } // Expand the tree and select the first node treeSections.ExpandAll(); treeSections.SelectedNode = treeSections.Nodes[0].Nodes[0]; }
/// <summary> /// Check and refresh the view for each repo /// This method also enables the form's Close button if all repos are checked OK /// </summary> private void RefreshView() { list.Columns[1].Width = 75; bool enableClosing = true; foreach (ListViewItem item in list.Items) { ClassRepo repo = item.Tag as ClassRepo; item.Text = repo.Path; ClassUtils.DirStatType stat = ClassUtils.DirStat(repo.Path); switch (stat) { case ClassUtils.DirStatType.Invalid: item.SubItems[1].Text = "Non-existing"; enableClosing = false; item.SubItems[0].ForeColor = Color.Red; break; case ClassUtils.DirStatType.Empty: item.SubItems[1].Text = "Empty"; enableClosing = false; break; case ClassUtils.DirStatType.Git: item.SubItems[1].Text = "OK"; item.SubItems[0].ForeColor = Color.DarkOliveGreen; break; case ClassUtils.DirStatType.Nongit: item.SubItems[1].Text = "Non-empty"; enableClosing = false; break; } } btClose.Enabled = enableClosing; }
public FormRepoEdit(ClassRepo targetRepo) { InitializeComponent(); ClassWinGeometry.Restore(this); repo = targetRepo; // Get all local configuration strings and assign various panel controls. // This is placed first, before initializing the user panels, so that the // strings are accessible to individual panels should they need to use them. // TODO: This is not needed for now //string[] config = _repo.Run("config --local --list -z").Split('\0'); // Add all user panels to the base repo edit panel; call their init foreach (KeyValuePair <string, UserControl> key in panels) { panel.Controls.Add(key.Value); (key.Value as IRepoSettings).Init(repo); key.Value.Dock = DockStyle.Fill; } // Expand the tree and select the first node treeSections.ExpandAll(); treeSections.SelectedNode = treeSections.Nodes[0].Nodes[0]; }
/// <summary> /// Class constructor /// </summary> public ClassStatus(ClassRepo repo) { Repo = repo; Status(); }
/// <summary> /// Set the new current repo. /// Given repo can be null in which case the first repo will be selected as current. /// </summary> public void SetCurrent(ClassRepo repo) { // Assign the new 'current' repo but if null, try to find the first valid repo Current = (repo == null && Repos.Count > 0)? Repos[0] : repo; if (Current != null) Current.Remotes.Refresh(Current); // Refresh the list of remote repos }
/// <summary> /// Load a set of repositories from a file. /// Returns true if load succeeded. /// </summary> public bool Load(string fileName) { bool ret = false; Repos = new List<ClassRepo>(); // Wrap the opening of a repository database with an outer handler try { using (FileStream file = new FileStream(fileName, FileMode.Open)) { try { BinaryFormatter rd = new BinaryFormatter(); Repos = (List<ClassRepo>)rd.Deserialize(file); string defaultRepo = (string)rd.Deserialize(file); // WAR: Mono 2.6.7 does not support serialization of a HashSet. foreach (var classRepo in Repos) classRepo.ExpansionReset(null); // Upon load, set the current based on the default repo Default = Repos.Find(r => r.Root == defaultRepo); SetCurrent(Default); // TODO: Commenting out since it takes long time when loading many repos, and it seems not needed //App.Repos.Refresh(); ret = true; } catch (Exception ex) { throw new ClassException(ex.Message); } } } catch (Exception ex) { App.PrintLogMessage(ex.Message, MessageType.Error); } return ret; }
/// <summary> /// Main form initialization method performs operations that may need the main /// window to have already been shown since we may invoke its UI handlers /// Optionally, open an existing git repo at the path given in the initRepo argument /// </summary> public bool Initialize(string initRepo) { // Load default set of repositories // If this is the first time run, initialize the default workspace file name if (string.IsNullOrEmpty(Properties.Settings.Default.WorkspaceFile)) { Properties.Settings.Default.WorkspaceFile = Path.Combine(App.AppHome, "repos.giw"); } string name = Properties.Settings.Default.WorkspaceFile; if (File.Exists(name)) // Even if the workspace does not exist at this point { if (!ClassWorkspace.Load(name)) // still use it's name and continue running since it { return(false); // will be saved on a program exit } } // Load custom tools App.CustomTools = ClassCustomTools.Load(DefaultCustomToolsFile); // If there are no tools on the list, find some local tools and add them if (App.CustomTools.Tools.Count == 0) { App.CustomTools.Tools.AddRange(ClassCustomTools.FindLocalTools()); } // If there is no current repo, switch the right panel view to Repos // Otherwise, restore the last view panel ChangeRightPanel(App.Repos.Current == null ? "Repos" : Properties.Settings.Default.viewRightPanel); // The user requested to open a specific repo with the --repo command line argument if (initRepo != null) { try { ClassRepo repo = App.Repos.Find(initRepo); if (repo == null) { repo = App.Repos.Add(ClassCommandLine.initRepo); } App.Repos.SetCurrent(repo); ChangeRightPanel("Revisions"); // Set the right pane to "Revisions" tab App.PrintStatusMessage("Opening repo " + repo, MessageType.General); } catch (Exception ex) { App.PrintLogMessage("Unable to open repo: " + ex.Message, MessageType.Error); App.PrintStatusMessage(ex.Message, MessageType.Error); } } // Usability improvement: When starting the app, check if the global user name // and email are defined and if not, open the settings dialog. This helps when // starting the app for the first time. if (string.IsNullOrEmpty(ClassConfig.GetGlobal("user.name")) && string.IsNullOrEmpty(ClassConfig.GetGlobal("user.email"))) { MenuOptions(null, null); } return(true); }
/// <summary> /// Build a tree view of commit nodes using hints from repo commit groups. /// Commit nodes have Tags containing absolute paths to files, /// while the relative paths (with respect to repo root) are simply in the Text. /// </summary> public static TreeNode BuildCommitsView(ClassRepo repo, List<string> list) { repo.Commits.Rebuild(list); // Step 1: Build the default list with all files under this status class TreeNode root = new TreeNode("Pending Changelists"); root.Tag = "root"; // Tag of the root node foreach (var c in repo.Commits.Bundle) { var commitNode = new TreeNode(GetCommitNodeText(c)) { Tag = c }; if (c.IsCollapsed) commitNode.Collapse(); foreach (var f in c.Files) { var tn = new TreeNode(f) { Tag = f }; commitNode.Nodes.Add(tn); } root.Nodes.Add(commitNode); } root.ExpandAll(); return root; }
/// <summary> /// Class constructor /// </summary> public ClassStatus(ClassRepo repo) { Repo = repo; }
/// <summary> /// Set the new current repo. /// Given repo can be null. /// </summary> public void SetCurrent(ClassRepo repo) { Current = repo; if (Current != null) Current.Remotes.Refresh(Current); // Refresh the list of remote repos }
/// <summary> /// Build a tree view of commit nodes using hints from repo commit groups. /// Commit nodes have Tags containing absolute paths to files, /// while the relative paths (with respect to repo root) are simply in the Text. /// </summary> public static TreeNode BuildCommitsView(ClassRepo repo, List<string> list) { repo.Commits.Rebuild(list); // Step 1: Build the default list with all files under this status class TreeNode root = new TreeNode("Pending Changelists"); root.Tag = "root"; // Tag of the root node foreach (var c in repo.Commits.Bundle) { TreeNode commitNode = new TreeNode(c.Description); commitNode.Tag = c; foreach (var f in c.Files) { TreeNode tn = new TreeNode(f); tn.Tag = f; commitNode.Nodes.Add(tn); } root.Nodes.Add(commitNode); } root.ExpandAll(); return root; }