def async_fetch_status_for_all_files(self, from_user, extra_files=[]): """ :param List(GPS.File) extra_files: files for which we need to set the status eventually """ s = self.set_status_for_all_files() # Do we need to reset the "ls-tree" cache ? After the initial # loading, this list no longer changes without also impacting the # output of "git status", so we do not need to execute it again. if from_user or self._non_default_files is None: all_files = [] # faster to update than a set yield join(self.__git_ls_tree(all_files), self.__git_status(s)) self._non_default_files = s.files_with_explicit_status now_default = set(all_files).difference(self._non_default_files) else: # Reuse caches: we do not need to recompute the full list of files # for git, since this will not change without also changing the # output of "git status". We also do not reset the default status # for all the files not in the git status output: that might be a # slow operation that is blocking GPS. Instead, we only reset the # default status for files that used to be in "git status" (for # instance modified files), and are no longer there (either after # a "reset" or a "commit"). yield self.__git_status(s) nondefault = s.files_with_explicit_status now_default = self._non_default_files.difference(nondefault) self._non_default_files = nondefault for f in now_default: s.set_status(f, GPS.VCS2.Status.UNMODIFIED) s.set_status_for_remaining_files()
def async_branches(self, visitor): url = '' p = self._svn(['info']) while True: line = yield p.wait_line() if line is None: break if line.startswith('URL: '): url = line[5:] break if url: # Assume the standard 'trunk', 'branches' and 'tags' naming parent = url while True: parent, tail = os.path.split(parent) if tail in ('trunk', 'branches', 'tags'): break yield join(self._branches(visitor, parent), self._tags(visitor, parent))
def async_fetch_status_for_all_files(self, from_user, extra_files=[]): """ :param List(GPS.File) extra_files: files for which we need to set the status eventually """ s = self.set_status_for_all_files() files = set(extra_files) # Do we need to reset the "ls-tree" cache ? After the initial # loading, this list no longer changes without also impacting the # output of "git status", so we do not need to execute it again. if from_user or self._non_default_files is None: all_files = [] # faster to update than a set yield join(self.__git_ls_tree(all_files), self.__git_status(s)) self._non_default_files = s.files_with_explicit_status files.update(all_files) else: # Reuse caches: we do not need to recompute the full list of files # for git, since this will not change without also changing the # output of "git status". We also do not reset the default status # for all the files not in the git status output: that might be a # slow operation that is blocking GPS. Instead, we only reset the # default status for files that used to be in "git status" (for # instance modified files), and are no longer there (either after # a "reset" or a "commit"). yield self.__git_status(s) nondefault = s.files_with_explicit_status now_default = self._non_default_files.difference(nondefault) self._non_default_files = nondefault for f in now_default: s.set_status(f, self.default_status) s.set_status_for_remaining_files(files)
def async_action_on_branch(self, visitor, action, category, id, text=''): if category == CAT_BRANCHES: if action == GPS.VCS2.Actions.DOUBLE_CLICK and id: p = self._git(['checkout', id]) yield p.wait_until_terminate(show_if_error=True) elif action == GPS.VCS2.Actions.TOOLTIP and id: visitor.tooltip( '\nDouble-click to checkout this branch.\n' 'Click [+] to create a new branch from this one.\n' 'Click [-] to delete current branch.') elif action == GPS.VCS2.Actions.ADD and id: name = GPS.MDI.input_dialog('Choose a name for the new branch', 'name=%s-new' % id) if name: name = name[0] p = self._git(['branch', '--track', name, id]) s, _ = yield p.wait_until_terminate(show_if_error=True) if s == 0: # Checkout will not succeed if there are local changes p = self._git(['checkout', name]) yield p.wait_until_terminate(show_if_error=True) elif action == GPS.VCS2.Actions.RENAME and id and text: p = self._git(['branch', '-m', id, text]) yield p.wait_until_terminate(show_if_error=True) elif action == GPS.VCS2.Actions.REMOVE and id: if (id != 'master' and GPS.MDI.yes_no_dialog( "Delete branch `%s` ?" % id)): # If this is the current branch, fallback to master current = yield self._current_branch() if current == id: p = self._git(['checkout', 'master']) s, _ = yield p.wait_until_terminate(show_if_error=True) p = self._git(['branch', '-D', id]) yield p.wait_until_terminate(show_if_error=True) elif category == CAT_TAGS: if action == GPS.VCS2.Actions.DOUBLE_CLICK and id: p = self._git(['checkout', id]) yield p.wait_until_terminate(show_if_error=True) elif action == GPS.VCS2.Actions.TOOLTIP: visitor.tooltip('\nDouble-click to checkout this tag' + ( '\nClick [+] to create a new tag on current branch' if not id else '') + ('\nClick [-] to delete tag' if id else '')) elif action == GPS.VCS2.Actions.ADD and not id: name = GPS.MDI.input_dialog( 'Choose a name for the new tag', 'name', 'Commit Message (will annotate if set)') if name and name[0]: # not cancelled p = self._git([ 'tag', '-a' if name[1] else '', '--message=%s' % name[1] if name[1] else '', name[0] ]) yield p.wait_until_terminate(show_if_error=True) elif action == GPS.VCS2.Actions.REMOVE: if id and GPS.MDI.yes_no_dialog("Delete tag `%s` ?" % id): p = self._git(['tag', '-d', id]) yield p.wait_until_terminate(show_if_error=True) elif action == GPS.VCS2.Actions.RENAME and id and text: # ??? Can we create an annotated tag ? p = self._git(['tag', text, id]) s, _ = yield p.wait_until_terminate(show_if_error=True) if s == 0: p = self._git(['tag', '-d', id]) s, _ = yield p.wait_until_terminate(show_if_error=True) # ??? Should we push to origin to remote ? elif category == CAT_STASHES: if action == GPS.VCS2.Actions.DOUBLE_CLICK and id: p = self._git(['stash', 'apply', id]) yield p.wait_until_terminate(show_if_error=True) elif action == GPS.VCS2.Actions.TOOLTIP: visitor.tooltip( ('\nDouble-click to apply this stash on HEAD' + '\nClick [-] to drop this stash' if id else '') + ('' if id else '\nClick [+] to stash all local changes')) elif action == GPS.VCS2.Actions.ADD and not id: p = self._git(['stash', 'save', 'created from GPS']) yield p.wait_until_terminate(show_if_error=True) elif action == GPS.VCS2.Actions.REMOVE and id: p = self._git(['stash', 'drop', id]) yield p.wait_until_terminate(show_if_error=True) elif category == CAT_REMOTES: if action == GPS.VCS2.Actions.DOUBLE_CLICK: pass elif action == GPS.VCS2.Actions.TOOLTIP: visitor.tooltip( '\nDouble-click to checkout this remote branch locally' + '\nClick [-] to delete this remote branch' if id else '') pass elif action == GPS.VCS2.Actions.ADD: pass elif action == GPS.VCS2.Actions.REMOVE and id: # id is of the form 'remotes/origin/some/name' _, origin, name = id.split('/', 2) if GPS.MDI.yes_no_dialog("Delete remote branch `%s` ?" % id): p = self._git(['push', origin, ':%s' % name]) yield p.wait_until_terminate(show_if_error=True) elif category in (CAT_WORKTREES, CAT_SUBMODULES): pass else: yield join(*self.extensions('async_action_on_branch', visitor, action, category, id, text))
def async_branches(self, visitor): yield join(self._branches(visitor), self._tags(visitor), self._stashes(visitor), self._worktrees(visitor), self._submodules(visitor), *self.extensions('async_branches', visitor))
def async_fetch_history(self, visitor, filter): # Compute, in parallel, needed pieces of information (unpushed, has_local) = yield join(self._unpushed_local_changes(), self._has_local_changes()) # Then fetch the history max_lines = filter[0] for_file = filter[1] pattern = filter[2] current_branch_only = filter[3] branch_commits_only = filter[4] filter_switch = '' if pattern: if pattern.startswith('author:'): filter_switch = '--author=%s' % pattern[7:] elif pattern.startswith('code:'): filter_switch = '-S=%s' % pattern[5:] else: filter_switch = '--grep=%s' % pattern git_cmd = [ 'log', # use tformat to get final newline '--pretty=tformat:%H@@%P@@%an@@%D@@%cD@@%s' ] if not current_branch_only: git_cmd.append('--branches') git_cmd.append('--tags') git_cmd.append('--remotes') if for_file: git_cmd.append('--follow') git_cmd += [ '--topo-order', # children before parents filter_switch, '--max-count=%d' % max_lines if not branch_commits_only else '', '%s' % for_file.path if for_file else '' ] p = self._git(git_cmd) nb_added_lines = 0 while True: line = yield p.wait_line() if line is None or '@@' not in line: GPS.Logger("GIT").log("finished git-status") break id, parents, author, branches, date, subject = line.split('@@') parents = parents.split() branches = None if not branches else branches.split(',') flags = 0 if id in unpushed: flags |= GPS.VCS2.Commit.Flags.UNPUSHED if branches is None: branch_descr = None else: branch_descr = [] for b in branches: b = b.strip() # ??? How do we detect other remotes if b.startswith('origin/'): f = (b, GPS.VCS2.Commit.Kind.REMOTE) elif b.startswith("HEAD"): f = (b, GPS.VCS2.Commit.Kind.HEAD) # Append a dummy entry if we have local changes, and # we have the HEAD if has_local: visitor.history_line( GPS.VCS2.Commit( LOCAL_CHANGES_ID, '', '', '<uncommitted changes>', parents=[id], flags=GPS.VCS2.Commit.Flags.UNCOMMITTED | GPS.VCS2.Commit.Flags.UNPUSHED)) elif b.startswith("tag: "): f = (b[5:], GPS.VCS2.Commit.Kind.TAG) else: f = (b, GPS.VCS2.Commit.Kind.LOCAL) branch_descr.append(f) visitor.history_line( GPS.VCS2.Commit(id, author, date, subject, parents, branch_descr, flags=flags)) nb_added_lines += 1 GPS.Logger("GIT").log("done parsing git-log (%s lines)" % (nb_added_lines, ))
def async_fetch_history(self, visitor, filter): # Compute, in parallel, needed pieces of information (unpushed, has_local) = yield join(self._unpushed_local_changes(), self._has_local_changes()) # Then fetch the history max_lines = filter[0] for_file = filter[1] pattern = filter[2] current_branch_only = filter[3] branch_commits_only = filter[4] filter_switch = '' if pattern: if pattern.startswith('author:'): filter_switch = '--author=%s' % pattern[7:] elif pattern.startswith('code:'): filter_switch = '-S=%s' % pattern[5:] else: filter_switch = '--grep=%s' % pattern p = self._git([ 'log', # use tformat to get final newline '--pretty=tformat:%H@@%P@@%an@@%D@@%cD@@%s', '--branches' if not current_branch_only else '', '--tags' if not current_branch_only else '', '--remotes' if not current_branch_only else '', '--follow', '--topo-order', # children before parents filter_switch, '--max-count=%d' % max_lines if not branch_commits_only else '', '%s' % for_file.path if for_file else '' ]) children = {} # number of children for each sha1 result = [] count = 0 while True: line = yield p.wait_line() if line is None or '@@' not in line: GPS.Logger("GIT").log("finished git-status") break id, parents, author, branches, date, subject = line.split('@@') parents = parents.split() branches = None if not branches else branches.split(',') has_head = None flags = 0 if id in unpushed: flags |= GPS.VCS2.Commit.Flags.UNPUSHED if branches is None: branch_descr = None else: branch_descr = [] for b in branches: b = b.strip() # ??? How do we detect other remotes if b.startswith('origin/'): f = (b, GPS.VCS2.Commit.Kind.REMOTE) elif b.startswith("HEAD"): has_head = id f = (b, GPS.VCS2.Commit.Kind.HEAD) elif b.startswith("tag: "): f = (b[5:], GPS.VCS2.Commit.Kind.TAG) else: f = (b, GPS.VCS2.Commit.Kind.LOCAL) branch_descr.append(f) # Append a dummy entry if we have local changes, and # we have the HEAD if has_head is not None and has_local: result.insert( 0, GPS.VCS2.Commit(LOCAL_CHANGES_ID, '', '', '<uncommitted changes>', parents=[has_head], flags=GPS.VCS2.Commit.Flags.UNCOMMITTED | GPS.VCS2.Commit.Flags.UNPUSHED)) current = GPS.VCS2.Commit(id, author, date, subject, parents, branch_descr, flags=flags) if branch_commits_only: for pa in parents: children[pa] = children.setdefault(pa, 0) + 1 # Count only relevant commits if (len(parents) > 1 or branches is not None or id not in children or children[id] > 1): count += 1 result.append(current) if count >= max_lines: break GPS.Logger("GIT").log("done parsing git-log (%s lines)" % (len(result), )) visitor.history_lines(result)
def async_branches(self, visitor): def __branches(): branches = [] remotes = [] r = re.compile( "^(?P<current>\*)?\s+" "(?P<name>\S+)" "\s+" "(?P<id>[a-z0-9]+)" "\s+" "(\[(?P<tracking>.*\]))?") emblem_r = re.compile( "^(?P<tracking>.*):\s" "(ahead (?P<ahead>\d+),?)?\s*" "(behind (?P<behind>\d+))?") p = self._git(['branch', '-a', '--list', '--no-color', '-vv']) while True: line = yield p.wait_line() if line is None: visitor.branches( 'branches', 'vcs-branch-symbolic', branches) visitor.branches('remotes', 'vcs-cloud-symbolic', remotes) break m = r.search(line) if m: n = m.group('name') emblem = [] m2 = emblem_r.search(m.group('tracking') or '') if m2: n = '%s (%s)' % (n, m2.group('tracking')) if m2.group('ahead'): emblem.append("%s%s%s%s" % ( chr(226), chr(134), chr(145), m2.group('ahead'))) if m2.group('behind'): emblem.append("%s%s%s%s" % ( chr(226), chr(134), chr(147), m2.group('behind'))) emblem = ' '.join(emblem) if n.startswith('remotes/'): remotes.append( (n[8:], m.group('current') is not None, emblem, m.group('name'))) else: branches.append( (n, m.group('current') is not None, emblem, m.group('name'))) def __tags(): p = self._git(['tag']) tags = [] while True: line = yield p.wait_line() if line is None: visitor.branches('tags', 'vcs-tag-symbolic', tags) break tags.append((line, False, '', line)) def __stashes(): p = self._git(['stash', 'list']) stashes = [] while True: line = yield p.wait_line() if line is None: visitor.branches('stashes', 'vcs-stash-symbolic', stashes) break name, branch, descr = line.split(':', 3) stashes.append(('%s: %s' % (name, descr), False, branch, name)) a = yield join(__branches(), __tags(), __stashes())