def get_file_history(self, path, limit=None): """ Returns history of file as reversed list of ``Changeset`` objects for which file at given ``path`` has been modified. TODO: This function now uses os underlying 'git' and 'grep' commands which is generally not good. Should be replaced with algorithm iterating commits. """ self._get_filectx(path) cs_id = safe_str(self.id) f_path = safe_str(path) if limit: cmd = 'log -n %s --pretty="format: %%H" -s -p %s -- "%s"' % ( safe_int(limit, 0), cs_id, f_path ) else: cmd = 'log --pretty="format: %%H" -s -p %s -- "%s"' % ( cs_id, f_path ) so, se = self.repository.run_git_command(cmd) ids = re.findall(r'[0-9a-fA-F]{40}', so) return [self.repository.get_changeset(id) for id in ids]
def _get_id_for_path(self, path): path = safe_str(path) # FIXME: Please, spare a couple of minutes and make those codes cleaner; if not path in self._paths: path = path.strip('/') # set root tree tree = self.repository._repo[self._tree_id] if path == '': self._paths[''] = tree.id return tree.id splitted = path.split('/') dirs, name = splitted[:-1], splitted[-1] curdir = '' # initially extract things from root dir for item, stat, id in tree.iteritems(): if curdir: name = '/'.join((curdir, item)) else: name = item self._paths[name] = id self._stat_modes[name] = stat for dir in dirs: if curdir: curdir = '/'.join((curdir, dir)) else: curdir = dir dir_id = None for item, stat, id in tree.iteritems(): if dir == item: dir_id = id if dir_id: # Update tree tree = self.repository._repo[dir_id] if not isinstance(tree, objects.Tree): raise ChangesetError('%s is not a directory' % curdir) else: raise ChangesetError('%s have not been found' % curdir) # cache all items from the given traversed tree for item, stat, id in tree.iteritems(): if curdir: name = '/'.join((curdir, item)) else: name = item self._paths[name] = id self._stat_modes[name] = stat if not path in self._paths: raise NodeDoesNotExistError( "There is no file nor directory " "at the given path '%s' at revision %s" % (path, safe_str(self.short_id))) return self._paths[path]
def _get_id_for_path(self, path): path = safe_str(path) # FIXME: Please, spare a couple of minutes and make those codes cleaner; if not path in self._paths: path = path.strip('/') # set root tree tree = self.repository._repo[self._tree_id] if path == '': self._paths[''] = tree.id return tree.id splitted = path.split('/') dirs, name = splitted[:-1], splitted[-1] curdir = '' # initially extract things from root dir for item, stat, id in tree.iteritems(): if curdir: name = '/'.join((curdir, item)) else: name = item self._paths[name] = id self._stat_modes[name] = stat for dir in dirs: if curdir: curdir = '/'.join((curdir, dir)) else: curdir = dir dir_id = None for item, stat, id in tree.iteritems(): if dir == item: dir_id = id if dir_id: # Update tree tree = self.repository._repo[dir_id] if not isinstance(tree, objects.Tree): raise ChangesetError('%s is not a directory' % curdir) else: raise ChangesetError('%s have not been found' % curdir) # cache all items from the given traversed tree for item, stat, id in tree.iteritems(): if curdir: name = '/'.join((curdir, item)) else: name = item self._paths[name] = id self._stat_modes[name] = stat if not path in self._paths: raise NodeDoesNotExistError("There is no file nor directory " "at the given path '%s' at revision %s" % (path, safe_str(self.short_id))) return self._paths[path]
def __get_rev_or_redirect(self, ref, repo, redirect_after=True, partial=False): """ Safe way to get changeset if error occur it redirects to changeset with proper message. If partial is set then don't do redirect raise Exception instead :param rev: revision to fetch :param repo: repo instance """ rev = ref[1] # default and used for git if repo.scm_instance.alias == 'hg': # lookup up the exact node id _revset_predicates = { 'branch': 'branch', 'book': 'bookmark', 'tag': 'tag', 'rev': 'id', } rev_spec = "max(%s(%%s))" % _revset_predicates[ref[0]] revs = repo.scm_instance._repo.revs(rev_spec, safe_str(ref[1])) if revs: rev = revs[-1] # else: TODO: just report 'not found' try: return repo.scm_instance.get_changeset(rev).raw_id except EmptyRepositoryError, e: if not redirect_after: return None h.flash(h.literal(_('There are no changesets yet')), category='warning') redirect(url('summary_home', repo_name=repo.repo_name))
def get_file_mode(self, path): """ Returns stat mode of the file at the given ``path``. """ # ensure path is traversed path = safe_str(path) self._get_id_for_path(path) return self._stat_modes[path]
def _fix_path(self, path): """ Paths are stored without trailing slash so we need to get rid off it if needed. Also mercurial keeps filenodes as str so we need to decode from unicode to str """ if path.endswith('/'): path = path.rstrip('/') return safe_str(path)
def __init__(self, path, kind): if path.startswith('/'): raise NodeError("Cannot initialize Node objects with slash at " "the beginning as only relative paths are supported") self.path = safe_str(path.rstrip('/')) # we store paths as str if path == '' and kind != NodeKind.DIR: raise NodeError("Only DirNode and its subclasses may be " "initialized with empty path") self.kind = kind #self.dirs, self.files = [], [] if self.is_root() and not self.is_dir(): raise NodeError("Root node cannot be FILE kind")
def __init__(self, path, kind): if path.startswith('/'): raise NodeError( "Cannot initialize Node objects with slash at " "the beginning as only relative paths are supported") self.path = safe_str(path.rstrip('/')) # we store paths as str if path == '' and kind != NodeKind.DIR: raise NodeError("Only DirNode and its subclasses may be " "initialized with empty path") self.kind = kind #self.dirs, self.files = [], [] if self.is_root() and not self.is_dir(): raise NodeError("Root node cannot be FILE kind")
def get_file_history(self, path, limit=None): """ Returns history of file as reversed list of ``Changeset`` objects for which file at given ``path`` has been modified. TODO: This function now uses os underlying 'git' and 'grep' commands which is generally not good. Should be replaced with algorithm iterating commits. """ self._get_filectx(path) cs_id = safe_str(self.id) f_path = safe_str(path) if limit: cmd = 'log -n %s --pretty="format: %%H" -s -p %s -- "%s"' % ( safe_int(limit, 0), cs_id, f_path) else: cmd = 'log --pretty="format: %%H" -s -p %s -- "%s"' % (cs_id, f_path) so, se = self.repository.run_git_command(cmd) ids = re.findall(r'[0-9a-fA-F]{40}', so) return [self.repository.get_changeset(id) for id in ids]
class CompareController(BaseRepoController): def __before__(self): super(CompareController, self).__before__() def __get_rev_or_redirect(self, ref, repo, redirect_after=True, partial=False): """ Safe way to get changeset if error occur it redirects to changeset with proper message. If partial is set then don't do redirect raise Exception instead :param rev: revision to fetch :param repo: repo instance """ rev = ref[1] # default and used for git if repo.scm_instance.alias == 'hg': # lookup up the exact node id _revset_predicates = { 'branch': 'branch', 'book': 'bookmark', 'tag': 'tag', 'rev': 'id', } rev_spec = "max(%s(%%s))" % _revset_predicates[ref[0]] revs = repo.scm_instance._repo.revs(rev_spec, safe_str(ref[1])) if revs: rev = revs[-1] # else: TODO: just report 'not found' try: return repo.scm_instance.get_changeset(rev).raw_id except EmptyRepositoryError, e: if not redirect_after: return None h.flash(h.literal(_('There are no changesets yet')), category='warning') redirect(url('summary_home', repo_name=repo.repo_name)) except RepositoryError, e: log.error(traceback.format_exc()) h.flash(safe_str(e), category='warning') if not partial: redirect(h.url('summary_home', repo_name=repo.repo_name)) raise HTTPBadRequest()
def commit(self, message, author, parents=None, branch=None, date=None, **kwargs): """ Performs in-memory commit (doesn't check workdir in any way) and returns newly created ``Changeset``. Updates repository's ``revisions``. :param message: message of the commit :param author: full username, i.e. "Joe Doe <*****@*****.**>" :param parents: single parent or sequence of parents from which commit would be derieved :param date: ``datetime.datetime`` instance. Defaults to ``datetime.datetime.now()``. :param branch: branch name, as string. If none given, default backend's branch would be used. :raises ``CommitError``: if any error occurs while committing """ self.check_integrity(parents) from .repository import GitRepository if branch is None: branch = GitRepository.DEFAULT_BRANCH_NAME repo = self.repository._repo object_store = repo.object_store ENCODING = "UTF-8" DIRMOD = 040000 # Create tree and populates it with blobs commit_tree = self.parents[0] and repo[self.parents[0]._commit.tree] or\ objects.Tree() for node in self.added + self.changed: # Compute subdirs if needed dirpath, nodename = posixpath.split(node.path) dirnames = dirpath and dirpath.split('/') or [] parent = commit_tree ancestors = [('', parent)] # Tries to dig for the deepest existing tree while dirnames: curdir = dirnames.pop(0) try: dir_id = parent[curdir][1] except KeyError: # put curdir back into dirnames and stops dirnames.insert(0, curdir) break else: # If found, updates parent parent = self.repository._repo[dir_id] ancestors.append((curdir, parent)) # Now parent is deepest existing tree and we need to create subtrees # for dirnames (in reverse order) [this only applies for nodes from added] new_trees = [] if not node.is_binary: content = node.content.encode(ENCODING) else: content = node.content blob = objects.Blob.from_string(content) node_path = node.name.encode(ENCODING) if dirnames: # If there are trees which should be created we need to build # them now (in reverse order) reversed_dirnames = list(reversed(dirnames)) curtree = objects.Tree() curtree[node_path] = node.mode, blob.id new_trees.append(curtree) for dirname in reversed_dirnames[:-1]: newtree = objects.Tree() #newtree.add(DIRMOD, dirname, curtree.id) newtree[dirname] = DIRMOD, curtree.id new_trees.append(newtree) curtree = newtree parent[reversed_dirnames[-1]] = DIRMOD, curtree.id else: parent.add(name=node_path, mode=node.mode, hexsha=blob.id) new_trees.append(parent) # Update ancestors for parent, tree, path in reversed([(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])]): parent[path] = DIRMOD, tree.id object_store.add_object(tree) object_store.add_object(blob) for tree in new_trees: object_store.add_object(tree) for node in self.removed: paths = node.path.split('/') tree = commit_tree trees = [tree] # Traverse deep into the forest... for path in paths: try: obj = self.repository._repo[tree[path][1]] if isinstance(obj, objects.Tree): trees.append(obj) tree = obj except KeyError: break # Cut down the blob and all rotten trees on the way back... for path, tree in reversed(zip(paths, trees)): del tree[path] if tree: # This tree still has elements - don't remove it or any # of it's parents break object_store.add_object(commit_tree) # Create commit commit = objects.Commit() commit.tree = commit_tree.id commit.parents = [p._commit.id for p in self.parents if p] commit.author = commit.committer = safe_str(author) commit.encoding = ENCODING commit.message = safe_str(message) # Compute date if date is None: date = time.time() elif isinstance(date, datetime.datetime): date = time.mktime(date.timetuple()) author_time = kwargs.pop('author_time', date) commit.commit_time = int(date) commit.author_time = int(author_time) tz = time.timezone author_tz = kwargs.pop('author_timezone', tz) commit.commit_timezone = tz commit.author_timezone = author_tz object_store.add_object(commit) ref = 'refs/heads/%s' % branch repo.refs[ref] = commit.id repo.refs.set_symbolic_ref('HEAD', ref) # Update vcs repository object & recreate dulwich repo self.repository.revisions.append(commit.id) self.repository._repo = Repo(self.repository.path) # invalidate parsed refs after commit self.repository._parsed_refs = self.repository._get_parsed_refs() tip = self.repository.get_changeset() self.reset() return tip
def commit(self, message, author, parents=None, branch=None, date=None, **kwargs): """ Performs in-memory commit (doesn't check workdir in any way) and returns newly created ``Changeset``. Updates repository's ``revisions``. :param message: message of the commit :param author: full username, i.e. "Joe Doe <*****@*****.**>" :param parents: single parent or sequence of parents from which commit would be derieved :param date: ``datetime.datetime`` instance. Defaults to ``datetime.datetime.now()``. :param branch: branch name, as string. If none given, default backend's branch would be used. :raises ``CommitError``: if any error occurs while committing """ self.check_integrity(parents) from .repository import GitRepository if branch is None: branch = GitRepository.DEFAULT_BRANCH_NAME repo = self.repository._repo object_store = repo.object_store ENCODING = "UTF-8" DIRMOD = 040000 # Create tree and populates it with blobs commit_tree = self.parents[0] and repo[self.parents[0]._commit.tree] or\ objects.Tree() for node in self.added + self.changed: # Compute subdirs if needed dirpath, nodename = posixpath.split(node.path) dirnames = dirpath and dirpath.split('/') or [] parent = commit_tree ancestors = [('', parent)] # Tries to dig for the deepest existing tree while dirnames: curdir = dirnames.pop(0) try: dir_id = parent[curdir][1] except KeyError: # put curdir back into dirnames and stops dirnames.insert(0, curdir) break else: # If found, updates parent parent = self.repository._repo[dir_id] ancestors.append((curdir, parent)) # Now parent is deepest existing tree and we need to create subtrees # for dirnames (in reverse order) [this only applies for nodes from added] new_trees = [] if not node.is_binary: content = node.content.encode(ENCODING) else: content = node.content blob = objects.Blob.from_string(content) node_path = node.name.encode(ENCODING) if dirnames: # If there are trees which should be created we need to build # them now (in reverse order) reversed_dirnames = list(reversed(dirnames)) curtree = objects.Tree() curtree[node_path] = node.mode, blob.id new_trees.append(curtree) for dirname in reversed_dirnames[:-1]: newtree = objects.Tree() #newtree.add(DIRMOD, dirname, curtree.id) newtree[dirname] = DIRMOD, curtree.id new_trees.append(newtree) curtree = newtree parent[reversed_dirnames[-1]] = DIRMOD, curtree.id else: parent.add(name=node_path, mode=node.mode, hexsha=blob.id) new_trees.append(parent) # Update ancestors for parent, tree, path in reversed([ (a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:]) ]): parent[path] = DIRMOD, tree.id object_store.add_object(tree) object_store.add_object(blob) for tree in new_trees: object_store.add_object(tree) for node in self.removed: paths = node.path.split('/') tree = commit_tree trees = [tree] # Traverse deep into the forest... for path in paths: try: obj = self.repository._repo[tree[path][1]] if isinstance(obj, objects.Tree): trees.append(obj) tree = obj except KeyError: break # Cut down the blob and all rotten trees on the way back... for path, tree in reversed(zip(paths, trees)): del tree[path] if tree: # This tree still has elements - don't remove it or any # of it's parents break object_store.add_object(commit_tree) # Create commit commit = objects.Commit() commit.tree = commit_tree.id commit.parents = [p._commit.id for p in self.parents if p] commit.author = commit.committer = safe_str(author) commit.encoding = ENCODING commit.message = safe_str(message) # Compute date if date is None: date = time.time() elif isinstance(date, datetime.datetime): date = time.mktime(date.timetuple()) author_time = kwargs.pop('author_time', date) commit.commit_time = int(date) commit.author_time = int(author_time) tz = time.timezone author_tz = kwargs.pop('author_timezone', tz) commit.commit_timezone = tz commit.author_timezone = author_tz object_store.add_object(commit) ref = 'refs/heads/%s' % branch repo.refs[ref] = commit.id repo.refs.set_symbolic_ref('HEAD', ref) # Update vcs repository object & recreate dulwich repo self.repository.revisions.append(commit.id) # invalidate parsed refs after commit self.repository._parsed_refs = self.repository._get_parsed_refs() tip = self.repository.get_changeset() self.reset() return tip
def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None): """return a structure with repo's interesting changesets, suitable for the selectors in pullrequest.html rev: a revision that must be in the list somehow and selected by default branch: a branch that must be in the list and selected by default - even if closed branch_rev: a revision of which peers should be preferred and available.""" # list named branches that has been merged to this named branch - it should probably merge back peers = [] if rev: rev = safe_str(rev) if branch: branch = safe_str(branch) if branch_rev: branch_rev = safe_str(branch_rev) # not restricting to merge() would also get branch point and be better # (especially because it would get the branch point) ... but is currently too expensive otherbranches = {} for i in repo._repo.revs( "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))", branch_rev, branch_rev): cs = repo.get_changeset(i) otherbranches[cs.branch] = cs.raw_id for abranch, node in otherbranches.iteritems(): selected = 'branch:%s:%s' % (abranch, node) peers.append((selected, abranch)) selected = None branches = [] for abranch, branchrev in repo.branches.iteritems(): n = 'branch:%s:%s' % (abranch, branchrev) branches.append((n, abranch)) if rev == branchrev: selected = n if branch == abranch: selected = n branch = None if branch: # branch not in list - it is probably closed revs = repo._repo.revs('max(branch(%s))', branch) if revs: cs = repo.get_changeset(revs[0]) selected = 'branch:%s:%s' % (branch, cs.raw_id) branches.append((selected, branch)) bookmarks = [] for bookmark, bookmarkrev in repo.bookmarks.iteritems(): n = 'book:%s:%s' % (bookmark, bookmarkrev) bookmarks.append((n, bookmark)) if rev == bookmarkrev: selected = n tags = [] for tag, tagrev in repo.tags.iteritems(): n = 'tag:%s:%s' % (tag, tagrev) tags.append((n, tag)) if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better selected = n # prio 1: rev was selected as existing entry above # prio 2: create special entry for rev; rev _must_ be used specials = [] if rev and selected is None: selected = 'rev:%s:%s' % (rev, rev) specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))] # prio 3: most recent peer branch if peers and not selected: selected = peers[0][0][0] # prio 4: tip revision if not selected: selected = 'tag:tip:%s' % repo.tags['tip'] groups = [(specials, _("Special")), (peers, _("Peer branches")), (bookmarks, _("Bookmarks")), (branches, _("Branches")), (tags, _("Tags")), ] return [g for g in groups if g[0]], selected
def index(self, org_ref_type, org_ref, other_ref_type, other_ref): # org_ref will be evaluated in org_repo org_repo = c.rhodecode_db_repo.repo_name org_ref = (org_ref_type, org_ref) # other_ref will be evaluated in other_repo other_ref = (other_ref_type, other_ref) other_repo = request.GET.get('other_repo', org_repo) # If merge is True: # Show what org would get if merged with other: # List changesets that are ancestors of other but not of org. # New changesets in org is thus ignored. # Diff will be from common ancestor, and merges of org to other will thus be ignored. # If merge is False: # Make a raw diff from org to other, no matter if related or not. # Changesets in one and not in the other will be ignored merge = bool(request.GET.get('merge')) # fulldiff disables cut_off_limit c.fulldiff = request.GET.get('fulldiff') # partial uses compare_cs.html template directly partial = request.environ.get('HTTP_X_PARTIAL_XHR') # as_form puts hidden input field with changeset revisions c.as_form = partial and request.GET.get('as_form') # swap url for compare_diff page - never partial and never as_form c.swap_url = h.url('compare_url', repo_name=other_repo, org_ref_type=other_ref[0], org_ref=other_ref[1], other_repo=org_repo, other_ref_type=org_ref[0], other_ref=org_ref[1], merge=merge or '') org_repo = Repository.get_by_repo_name(org_repo) other_repo = Repository.get_by_repo_name(other_repo) if org_repo is None: log.error('Could not find org repo %s' % org_repo) raise HTTPNotFound if other_repo is None: log.error('Could not find other repo %s' % other_repo) raise HTTPNotFound if org_repo != other_repo and h.is_git(org_repo): log.error('compare of two remote repos not available for GIT REPOS') raise HTTPNotFound if org_repo.scm_instance.alias != other_repo.scm_instance.alias: log.error('compare of two different kind of remote repos not available') raise HTTPNotFound self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial) self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial) c.org_repo = org_repo c.other_repo = other_repo c.org_ref = org_ref[1] c.other_ref = other_ref[1] c.org_ref_type = org_ref[0] c.other_ref_type = other_ref[0] c.cs_ranges, c.ancestor = self._get_changesets(org_repo.scm_instance.alias, org_repo.scm_instance, org_ref, other_repo.scm_instance, other_ref, merge) c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in c.cs_ranges]) if not c.ancestor: log.warning('Unable to find ancestor revision') if partial: return render('compare/compare_cs.html') if c.ancestor: assert merge # case we want a simple diff without incoming changesets, # previewing what will be merged. # Make the diff on the other repo (which is known to have other_ref) log.debug('Using ancestor %s as org_ref instead of %s' % (c.ancestor, org_ref)) org_ref = ('rev', c.ancestor) org_repo = other_repo diff_limit = self.cut_off_limit if not c.fulldiff else None log.debug('running diff between %s and %s in %s' % (org_ref, other_ref, org_repo.scm_instance.path)) txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(org_ref[1]), rev2=safe_str(other_ref[1])) diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff', diff_limit=diff_limit) _parsed = diff_processor.prepare() c.limited_diff = False if isinstance(_parsed, LimitedDiffContainer): c.limited_diff = True c.files = [] c.changes = {} c.lines_added = 0 c.lines_deleted = 0 for f in _parsed: st = f['stats'] if not st['binary']: c.lines_added += st['added'] c.lines_deleted += st['deleted'] fid = h.FID('', f['filename']) c.files.append([fid, f['operation'], f['filename'], f['stats']]) htmldiff = diff_processor.as_html(enable_comments=False, parsed_lines=[f]) c.changes[fid] = [f['operation'], f['filename'], htmldiff] return render('compare/compare_diff.html')
def _load_compare_data(self, pull_request, enable_comments=True): """ Load context data needed for generating compare diff :param pull_request: """ org_repo = pull_request.org_repo (org_ref_type, org_ref_name, org_ref_rev) = pull_request.org_ref.split(':') other_repo = org_repo (other_ref_type, other_ref_name, other_ref_rev) = pull_request.other_ref.split(':') # despite opening revisions for bookmarks/branches/tags, we always # convert this to rev to prevent changes after bookmark or branch change org_ref = ('rev', org_ref_rev) other_ref = ('rev', other_ref_rev) c.org_repo = org_repo c.other_repo = other_repo c.fulldiff = fulldiff = request.GET.get('fulldiff') c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions] c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges]) c.org_ref = org_ref[1] c.org_ref_type = org_ref[0] c.other_ref = other_ref[1] c.other_ref_type = other_ref[0] diff_limit = self.cut_off_limit if not fulldiff else None # we swap org/other ref since we run a simple diff on one repo log.debug('running diff between %s and %s in %s' % (other_ref, org_ref, org_repo.scm_instance.path)) txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1])) diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff', diff_limit=diff_limit) _parsed = diff_processor.prepare() c.limited_diff = False if isinstance(_parsed, LimitedDiffContainer): c.limited_diff = True c.files = [] c.changes = {} c.lines_added = 0 c.lines_deleted = 0 for f in _parsed: st = f['stats'] c.lines_added += st['added'] c.lines_deleted += st['deleted'] fid = h.FID('', f['filename']) c.files.append([fid, f['operation'], f['filename'], f['stats']]) htmldiff = diff_processor.as_html(enable_comments=enable_comments, parsed_lines=[f]) c.changes[fid] = [f['operation'], f['filename'], htmldiff]
def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge): """ Returns a list of changesets that can be merged from org_repo@org_ref to other_repo@other_ref ... and the ancestor that would be used for merge :param org_repo: :param org_ref: :param other_repo: :param other_ref: :param tmp: """ ancestor = None if alias == 'hg': # lookup up the exact node id _revset_predicates = { 'branch': 'branch', 'book': 'bookmark', 'tag': 'tag', 'rev': 'id', } org_rev_spec = "max(%s(%%s))" % _revset_predicates[org_ref[0]] org_revs = org_repo._repo.revs(org_rev_spec, safe_str(org_ref[1])) org_rev = org_repo._repo[org_revs[-1] if org_revs else -1].hex() other_revs_spec = "max(%s(%%s))" % _revset_predicates[other_ref[0]] other_revs = other_repo._repo.revs(other_revs_spec, safe_str(other_ref[1])) other_rev = other_repo._repo[other_revs[-1] if other_revs else -1].hex() #case two independent repos if org_repo != other_repo: hgrepo = unionrepo.unionrepository(other_repo.baseui, other_repo.path, org_repo.path) # all the changesets we are looking for will be in other_repo, # so rev numbers from hgrepo can be used in other_repo #no remote compare do it on the same repository else: hgrepo = other_repo._repo if merge: revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)", other_rev, org_rev, org_rev) ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev, other_rev) if ancestors: # pick arbitrary ancestor - but there is usually only one ancestor = hgrepo[ancestors[0]].hex() else: # TODO: have both + and - changesets revs = hgrepo.revs("id(%s) :: id(%s) - id(%s)", org_rev, other_rev, org_rev) changesets = [other_repo.get_changeset(rev) for rev in revs] elif alias == 'git': if org_repo != other_repo: raise Exception('Comparing of different GIT repositories is not' 'allowed. Got %s != %s' % (org_repo, other_repo)) so, se = org_repo.run_git_command( 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1], other_ref[1]) ) changesets = [org_repo.get_changeset(cs) for cs in re.findall(r'[0-9a-fA-F]{40}', so)] return changesets, ancestor
def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge): """ Returns a list of changesets that can be merged from org_repo@org_ref to other_repo@other_ref ... and the ancestor that would be used for merge :param org_repo: :param org_ref: :param other_repo: :param other_ref: :param tmp: """ ancestor = None if alias == 'hg': # lookup up the exact node id _revset_predicates = { 'branch': 'branch', 'book': 'bookmark', 'tag': 'tag', 'rev': 'id', } org_rev_spec = "%s('%s')" % (_revset_predicates[org_ref[0]], safe_str(org_ref[1])) if org_ref[1] == EmptyChangeset().raw_id: org_rev = org_ref[1] else: org_rev = org_repo._repo[scmutil.revrange(org_repo._repo, [org_rev_spec])[-1]] other_rev_spec = "%s('%s')" % (_revset_predicates[other_ref[0]], safe_str(other_ref[1])) if other_ref[1] == EmptyChangeset().raw_id: other_rev = other_ref[1] else: other_rev = other_repo._repo[scmutil.revrange(other_repo._repo, [other_rev_spec])[-1]] #case two independent repos if org_repo != other_repo: hgrepo = unionrepo.unionrepository(other_repo.baseui, other_repo.path, org_repo.path) # all the changesets we are looking for will be in other_repo, # so rev numbers from hgrepo can be used in other_repo #no remote compare do it on the same repository else: hgrepo = other_repo._repo if merge: revs = ["ancestors(id('%s')) and not ancestors(id('%s')) and not id('%s')" % (other_rev, org_rev, org_rev)] ancestors = scmutil.revrange(hgrepo, ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)]) if len(ancestors) == 1: ancestor = hgrepo[ancestors[0]].hex() else: # TODO: have both + and - changesets revs = ["id('%s') :: id('%s') - id('%s')" % (org_rev, other_rev, org_rev)] changesets = [other_repo.get_changeset(cs) for cs in scmutil.revrange(hgrepo, revs)] elif alias == 'git': assert org_repo == other_repo, (org_repo, other_repo) # no git support for different repos so, se = org_repo.run_git_command( 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1], other_ref[1]) ) changesets = [org_repo.get_changeset(cs) for cs in re.findall(r'[0-9a-fA-F]{40}', so)] return changesets, ancestor
def _load_compare_data(self, pull_request, enable_comments=True): """ Load context data needed for generating compare diff :param pull_request: """ org_repo = pull_request.org_repo (org_ref_type, org_ref_name, org_ref_rev) = pull_request.org_ref.split(':') other_repo = org_repo (other_ref_type, other_ref_name, other_ref_rev) = pull_request.other_ref.split(':') # despite opening revisions for bookmarks/branches/tags, we always # convert this to rev to prevent changes after bookmark or branch change org_ref = ('rev', org_ref_rev) other_ref = ('rev', other_ref_rev) c.org_repo = org_repo c.other_repo = other_repo c.fulldiff = fulldiff = request.GET.get('fulldiff') c.cs_ranges = [ org_repo.get_changeset(x) for x in pull_request.revisions ] c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges]) c.org_ref = org_ref[1] c.org_ref_type = org_ref[0] c.other_ref = other_ref[1] c.other_ref_type = other_ref[0] diff_limit = self.cut_off_limit if not fulldiff else None # we swap org/other ref since we run a simple diff on one repo log.debug('running diff between %s and %s in %s' % (other_ref, org_ref, org_repo.scm_instance.path)) txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1])) diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff', diff_limit=diff_limit) _parsed = diff_processor.prepare() c.limited_diff = False if isinstance(_parsed, LimitedDiffContainer): c.limited_diff = True c.files = [] c.changes = {} c.lines_added = 0 c.lines_deleted = 0 for f in _parsed: st = f['stats'] c.lines_added += st['added'] c.lines_deleted += st['deleted'] fid = h.FID('', f['filename']) c.files.append([fid, f['operation'], f['filename'], f['stats']]) htmldiff = diff_processor.as_html(enable_comments=enable_comments, parsed_lines=[f]) c.changes[fid] = [f['operation'], f['filename'], htmldiff]
def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None): """return a structure with repo's interesting changesets, suitable for the selectors in pullrequest.html rev: a revision that must be in the list somehow and selected by default branch: a branch that must be in the list and selected by default - even if closed branch_rev: a revision of which peers should be preferred and available.""" # list named branches that has been merged to this named branch - it should probably merge back peers = [] if rev: rev = safe_str(rev) if branch: branch = safe_str(branch) if branch_rev: branch_rev = safe_str(branch_rev) # not restricting to merge() would also get branch point and be better # (especially because it would get the branch point) ... but is currently too expensive otherbranches = {} for i in repo._repo.revs( "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))", branch_rev, branch_rev): cs = repo.get_changeset(i) otherbranches[cs.branch] = cs.raw_id for abranch, node in otherbranches.iteritems(): selected = 'branch:%s:%s' % (abranch, node) peers.append((selected, abranch)) selected = None branches = [] for abranch, branchrev in repo.branches.iteritems(): n = 'branch:%s:%s' % (abranch, branchrev) branches.append((n, abranch)) if rev == branchrev: selected = n if branch == abranch: selected = n branch = None if branch: # branch not in list - it is probably closed revs = repo._repo.revs('max(branch(%s))', branch) if revs: cs = repo.get_changeset(revs[0]) selected = 'branch:%s:%s' % (branch, cs.raw_id) branches.append((selected, branch)) bookmarks = [] for bookmark, bookmarkrev in repo.bookmarks.iteritems(): n = 'book:%s:%s' % (bookmark, bookmarkrev) bookmarks.append((n, bookmark)) if rev == bookmarkrev: selected = n tags = [] for tag, tagrev in repo.tags.iteritems(): n = 'tag:%s:%s' % (tag, tagrev) tags.append((n, tag)) if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better selected = n # prio 1: rev was selected as existing entry above # prio 2: create special entry for rev; rev _must_ be used specials = [] if rev and selected is None: selected = 'rev:%s:%s' % (rev, rev) specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))] # prio 3: most recent peer branch if peers and not selected: selected = peers[0][0][0] # prio 4: tip revision if not selected: selected = 'tag:tip:%s' % repo.tags['tip'] groups = [ (specials, _("Special")), (peers, _("Peer branches")), (bookmarks, _("Bookmarks")), (branches, _("Branches")), (tags, _("Tags")), ] return [g for g in groups if g[0]], selected