def _get_repo(self, create, src_url=None, update_after_clone=False, bare=False): if create and os.path.exists(self.path): raise RepositoryError("Location already exist") if src_url and not create: raise RepositoryError("Create should be set to True if src_url is " "given (clone operation creates repository)") try: if create and src_url: GitRepository._check_url(src_url) self.clone(src_url, update_after_clone, bare) return Repo(self.path) elif create: os.makedirs(self.path) if bare: return Repo.init_bare(self.path) else: return Repo.init(self.path) else: return Repo(self.path) except (NotGitRepository, OSError) as err: raise RepositoryError(err)
def get_branch(self): headpath = self.repository._repo.refs.refpath('HEAD') try: content = open(headpath).read() match = re.match(r'^ref: refs/heads/(?P<branch>.+)\n$', content) if match: return match.groupdict()['branch'] else: raise RepositoryError("Couldn't compute workdir's branch") except IOError: # Try naive way... raise RepositoryError("Couldn't compute workdir's branch")
def _index(self, revision, method): c.anchor_url = anchor_url c.ignorews_url = _ignorews_url c.context_url = _context_url c.fulldiff = fulldiff = request.GET.get('fulldiff') #get ranges of revisions if preset rev_range = revision.split('...')[:2] enable_comments = True c.cs_repo = c.db_repo try: if len(rev_range) == 2: enable_comments = False rev_start = rev_range[0] rev_end = rev_range[1] rev_ranges = c.db_repo_scm_instance.get_changesets( start=rev_start, end=rev_end) else: rev_ranges = [c.db_repo_scm_instance.get_changeset(revision)] c.cs_ranges = list(rev_ranges) if not c.cs_ranges: raise RepositoryError('Changeset range returned empty result') except (ChangesetDoesNotExistError, ), e: log.error(traceback.format_exc()) msg = _('Such revision does not exist for this repository') h.flash(msg, category='error') raise HTTPNotFound()
def tag(self, name, user, revision=None, message=None, date=None, **kwargs): """ Creates and returns a tag for the given ``revision``. :param name: name for new tag :param user: full username, i.e.: "Joe Doe <*****@*****.**>" :param revision: changeset id for which new tag would be created :param message: message of the tag's commit :param date: date of tag's commit :raises TagAlreadyExistError: if tag with same name already exists """ if name in self.tags: raise TagAlreadyExistError("Tag %s already exists" % name) changeset = self.get_changeset(revision) local = kwargs.setdefault('local', False) if message is None: message = "Added tag %s for changeset %s" % (name, changeset.short_id) if date is None: date = datetime.datetime.now().ctime() try: self._repo.tag(name, changeset._ctx.node(), message, local, user, date) except Abort, e: raise RepositoryError(e.message)
def get_changesets(self, start=None, end=None, start_date=None, end_date=None, branch_name=None, reverse=False, max_revisions=None): """ Returns iterator of ``MercurialChangeset`` objects from start to end (both are inclusive) :param start: None, str, int or mercurial lookup format :param end: None, str, int or mercurial lookup format :param start_date: :param end_date: :param branch_name: :param reversed: return changesets in reversed order """ start_raw_id = self._get_revision(start) start_pos = None if start is None else self.revisions.index( start_raw_id) end_raw_id = self._get_revision(end) end_pos = None if end is None else self.revisions.index(end_raw_id) if start_pos is not None and end_pos is not None and start_pos > end_pos: raise RepositoryError("Start revision '%s' cannot be " "after end revision '%s'" % (start, end)) if branch_name and branch_name not in self.allbranches: msg = "Branch %r not found in %s" % (branch_name, self.name) raise BranchDoesNotExistError(msg) if end_pos is not None: end_pos += 1 # filter branches filter_ = [] if branch_name: filter_.append(b'branch("%s")' % safe_bytes(branch_name)) if start_date: filter_.append(b'date(">%s")' % safe_bytes(str(start_date))) if end_date: filter_.append(b'date("<%s")' % safe_bytes(str(end_date))) if filter_ or max_revisions: if filter_: revspec = b' and '.join(filter_) else: revspec = b'all()' if max_revisions: revspec = b'limit(%s, %d)' % (revspec, max_revisions) revisions = mercurial.scmutil.revrange(self._repo, [revspec]) else: revisions = self.revisions # this is very much a hack to turn this into a list; a better solution # would be to get rid of this function entirely and use revsets revs = list(revisions)[start_pos:end_pos] if reverse: revs.reverse() return CollectionGenerator(self, revs)
def remove_tag(self, name, user, message=None, date=None): """ Removes tag with the given ``name``. :param name: name of the tag to be removed :param user: full username, i.e.: "Joe Doe <*****@*****.**>" :param message: message of the tag's removal commit :param date: date of tag's removal commit :raises TagDoesNotExistError: if tag with given name does not exists """ if name not in self.tags: raise TagDoesNotExistError("Tag %s does not exist" % name) if message is None: message = "Removed tag %s" % name if date is None: date = safe_bytes( datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S')) local = False try: mercurial.tags.tag(self._repo, safe_bytes(name), mercurial.commands.nullid, safe_bytes(message), local, safe_bytes(user), date) self.tags = self._get_tags() except mercurial.error.Abort as e: raise RepositoryError(e.args[0])
def _get_repo(self, create, src_url=None, update_after_clone=False): """ Function will check for mercurial repository in given path and return a localrepo object. If there is no repository in that path it will raise an exception unless ``create`` parameter is set to True - in that case repository would be created and returned. If ``src_url`` is given, would try to clone repository from the location at given clone_point. Additionally it'll make update to working copy accordingly to ``update_after_clone`` flag """ try: if src_url: url = safe_str(self._get_url(src_url)) opts = {} if not update_after_clone: opts.update({'noupdate': True}) MercurialRepository._check_url(url, self.baseui) clone(self.baseui, url, self.path, **opts) # Don't try to create if we've already cloned repo create = False return localrepository(self.baseui, self.path, create=create) except (Abort, RepoError) as err: if create: msg = "Cannot create repository at %s. Original error was %s" \ % (self.path, err) else: msg = "Not valid repository at %s. Original error was %s" \ % (self.path, err) raise RepositoryError(msg)
def get_changesets(self, start=None, end=None, start_date=None, end_date=None, branch_name=None, reverse=False): """ Returns iterator of ``MercurialChangeset`` objects from start to end (both are inclusive) :param start: None, str, int or mercurial lookup format :param end: None, str, int or mercurial lookup format :param start_date: :param end_date: :param branch_name: :param reversed: return changesets in reversed order """ start_raw_id = self._get_revision(start) start_pos = self.revisions.index(start_raw_id) if start else None end_raw_id = self._get_revision(end) end_pos = self.revisions.index(end_raw_id) if end else None if None not in [start, end] and start_pos > end_pos: raise RepositoryError("Start revision '%s' cannot be " "after end revision '%s'" % (start, end)) if branch_name and branch_name not in self.allbranches.keys(): msg = ("Branch %s not found in %s" % (branch_name, self)) raise BranchDoesNotExistError(msg) if end_pos is not None: end_pos += 1 #filter branches filter_ = [] if branch_name: filter_.append('branch("%s")' % (branch_name)) if start_date and not end_date: filter_.append('date(">%s")' % start_date) if end_date and not start_date: filter_.append('date("<%s")' % end_date) if start_date and end_date: filter_.append('date(">%s") and date("<%s")' % (start_date, end_date)) if filter_: revisions = scmutil.revrange(self._repo, filter_) else: revisions = self.revisions # this is very much a hack to turn this into a list; a better solution # would be to get rid of this function entirely and use revsets revs = list(revisions)[start_pos:end_pos] if reverse: revs = reversed(revs) return CollectionGenerator(self, revs)
def get_tree_for_dir(tree, dirname): for name, mode, id in tree.iteritems(): if name == dirname: obj = self.repository._repo[id] if isinstance(obj, objects.Tree): return obj else: raise RepositoryError("Cannot create directory %s " "at tree %s as path is occupied and is not a " "Tree" % (dirname, tree)) return None
def mark_as_fork(self, repo, fork, user): repo = self.__get_repo(repo) fork = self.__get_repo(fork) if fork and repo.repo_id == fork.repo_id: raise Exception("Cannot set repository as fork of itself") if fork and repo.repo_type != fork.repo_type: raise RepositoryError("Cannot set repository as fork of repository with other type") repo.fork = fork return repo
def pull(self, url): """ Tries to pull changes from external location. """ other = mercurial.hg.peer(self._repo, {}, safe_bytes(self._get_url(url))) try: mercurial.exchange.pull(self._repo, other, heads=None, force=None) except mercurial.error.Abort as err: # Propagate error but with vcs's type raise RepositoryError(str(err))
def _run_git_command(cls, cmd, **opts): """ Runs given ``cmd`` as git command and returns tuple (stdout, stderr). :param cmd: git command to be executed :param opts: env options to pass into Subprocess command """ if '_bare' in opts: _copts = [] del opts['_bare'] else: _copts = [ '-c', 'core.quotepath=false', ] safe_call = False if '_safe' in opts: #no exc on failure del opts['_safe'] safe_call = True assert isinstance(cmd, list), cmd gitenv = os.environ # need to clean fix GIT_DIR ! if 'GIT_DIR' in gitenv: del gitenv['GIT_DIR'] gitenv['GIT_CONFIG_NOGLOBAL'] = '1' _git_path = settings.GIT_EXECUTABLE_PATH cmd = [_git_path] + _copts + cmd try: _opts = dict( env=gitenv, shell=False, ) _opts.update(opts) p = subprocessio.SubprocessIOChunker(cmd, **_opts) except (EnvironmentError, OSError) as err: tb_err = ("Couldn't run git command (%s).\n" "Original error was:%s\n" % (cmd, err)) log.error(tb_err) if safe_call: return '', err else: raise RepositoryError(tb_err) return ''.join(p.output), ''.join(p.error)
def pull(self, url): """ Tries to pull changes from external location. """ url = self._get_url(url) other = peer(self._repo, {}, url) try: # hg 3.2 moved push / pull to exchange module from mercurial import exchange exchange.pull(self._repo, other, heads=None, force=None) except ImportError: self._repo.pull(other, heads=None, force=None) except Abort as err: # Propagate error but with vcs's type raise RepositoryError(str(err))
def tag(self, name, user, revision=None, message=None, date=None, **kwargs): """ Creates and returns a tag for the given ``revision``. :param name: name for new tag :param user: full username, i.e.: "Joe Doe <*****@*****.**>" :param revision: changeset id for which new tag would be created :param message: message of the tag's commit :param date: date of tag's commit :raises TagAlreadyExistError: if tag with same name already exists """ if name in self.tags: raise TagAlreadyExistError("Tag %s already exists" % name) changeset = self.get_changeset(revision) local = kwargs.setdefault('local', False) if message is None: message = "Added tag %s for changeset %s" % (name, changeset.short_id) if date is None: date = safe_bytes( datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S')) try: mercurial.tags.tag(self._repo, safe_bytes(name), changeset._ctx.node(), safe_bytes(message), local, safe_bytes(user), date) except mercurial.error.Abort as e: raise RepositoryError(e.args[0]) # Reinitialize tags self.tags = self._get_tags() tag_id = self.tags[name] return self.get_changeset(revision=tag_id)
def remove_tag(self, name, user, message=None, date=None): """ Removes tag with the given ``name``. :param name: name of the tag to be removed :param user: full username, i.e.: "Joe Doe <*****@*****.**>" :param message: message of the tag's removal commit :param date: date of tag's removal commit :raises TagDoesNotExistError: if tag with given name does not exists """ if name not in self.tags: raise TagDoesNotExistError("Tag %s does not exist" % name) tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name) try: os.remove(tagpath) self._parsed_refs = self._get_parsed_refs() self.tags = self._get_tags() except OSError as e: raise RepositoryError(e.strerror)
def filectxfn(_repo, memctx, path): """ Marks given path as added/changed/removed in a given _repo. This is for internal mercurial commit function. """ # check if this path is removed if path in (node.path for node in self.removed): if getattr(memctx, '_returnnoneformissingfiles', False): return None else: # (hg < 3.2) Raising exception is the way to mark node for # removal raise IOError(errno.ENOENT, '%s is deleted' % path) # check if this path is added for node in self.added: if node.path == path: return memfilectx( _repo, path=node.path, data=(node.content.encode('utf8') if not node.is_binary else node.content), islink=False, isexec=node.is_executable, copied=False) # or changed for node in self.changed: if node.path == path: return memfilectx( _repo, path=node.path, data=(node.content.encode('utf8') if not node.is_binary else node.content), islink=False, isexec=node.is_executable, copied=False) raise RepositoryError("Given path haven't been marked as added," "changed or removed (%s)" % path)
def __get_filenode(self, cs, path): """ Returns file_node or raise HTTP error. :param cs: given changeset :param path: path to lookup """ try: file_node = cs.get_node(path) if file_node.is_dir(): raise RepositoryError('given path is a directory') except ChangesetDoesNotExistError: msg = _('Such revision does not exist for this repository') h.flash(msg, category='error') raise HTTPNotFound() except RepositoryError as e: h.flash(e, category='error') raise HTTPNotFound() return file_node
def _run_git_command(cls, cmd, cwd=None): """ Runs given ``cmd`` as git command and returns output bytes in a tuple (stdout, stderr) ... or raise RepositoryError. :param cmd: git command to be executed :param cwd: passed directly to subprocess """ # need to clean fix GIT_DIR ! gitenv = dict(os.environ) gitenv.pop('GIT_DIR', None) gitenv['GIT_CONFIG_NOGLOBAL'] = '1' assert isinstance(cmd, list), cmd cmd = [settings.GIT_EXECUTABLE_PATH, '-c', 'core.quotepath=false' ] + cmd try: p = subprocessio.SubprocessIOChunker(cmd, cwd=cwd, env=gitenv, shell=False) except (EnvironmentError, OSError) as err: # output from the failing process is in str(EnvironmentError) msg = ("Couldn't run git command %s.\n" "Subprocess failed with '%s': %s\n" % (cmd, type(err).__name__, err)).strip() log.error(msg) raise RepositoryError(msg) try: stdout = b''.join(p.output) stderr = b''.join(p.error) finally: p.close() # TODO: introduce option to make commands fail if they have any stderr output? if stderr: log.debug('stderr from %s:\n%s', cmd, stderr) else: log.debug('stderr from %s: None', cmd) return stdout, stderr
def __init__(self, repository, revision): self._stat_modes = {} self.repository = repository try: commit = self.repository._repo[ascii_bytes(revision)] if isinstance(commit, objects.Tag): revision = safe_str(commit.object[1]) commit = self.repository._repo.get_object(commit.object[1]) except KeyError: raise RepositoryError("Cannot get object with id %s" % revision) self.raw_id = ascii_str(commit.id) self.short_id = self.raw_id[:12] self._commit = commit # a Dulwich Commmit with .id self._tree_id = commit.tree self._committer_property = 'committer' self._author_property = 'author' self._date_property = 'commit_time' self._date_tz_property = 'commit_timezone' self.revision = repository.revisions.index(self.raw_id) self.nodes = {} self._paths = {}
def repo_scan(self, repos_path=None): """ Listing of repositories in given path. This path should not be a repository itself. Return a dictionary of repository objects mapping to vcs instances. :param repos_path: path to directory containing repositories """ if repos_path is None: repos_path = self.repos_path log.info('scanning for repositories in %s', repos_path) baseui = make_ui() repos = {} for name, path in get_filesystem_repos(repos_path): # name need to be decomposed and put back together using the / # since this is internal storage separator for kallithea name = Repository.normalize_repo_name(name) try: if name in repos: raise RepositoryError('Duplicate repository name %s ' 'found in %s' % (name, path)) else: klass = get_backend(path[0]) if path[0] == 'hg' and path[0] in BACKENDS: repos[name] = klass(path[1], baseui=baseui) if path[0] == 'git' and path[0] in BACKENDS: repos[name] = klass(path[1]) except OSError: continue log.debug('found %s paths with repositories', len(repos)) return repos
def filectxfn(_repo, memctx, bytes_path): """ Callback from Mercurial, returning ctx to commit for the given path. """ path = safe_str(bytes_path) # check if this path is removed if path in (node.path for node in self.removed): return None # check if this path is added for node in self.added: if node.path == path: return mercurial.context.memfilectx( _repo, memctx, path=bytes_path, data=node.content, islink=False, isexec=node.is_executable, copysource=False) # or changed for node in self.changed: if node.path == path: return mercurial.context.memfilectx( _repo, memctx, path=bytes_path, data=node.content, islink=False, isexec=node.is_executable, copysource=False) raise RepositoryError("Given path haven't been marked as added, " "changed or removed (%s)" % path)
def remove_tag(self, name, user, message=None, date=None): """ Removes tag with the given ``name``. :param name: name of the tag to be removed :param user: full username, i.e.: "Joe Doe <*****@*****.**>" :param message: message of the tag's removal commit :param date: date of tag's removal commit :raises TagDoesNotExistError: if tag with given name does not exists """ if name not in self.tags: raise TagDoesNotExistError("Tag %s does not exist" % name) if message is None: message = "Removed tag %s" % name if date is None: date = datetime.datetime.now().ctime() local = False try: self._repo.tag(name, nullid, message, local, user, date) self.tags = self._get_tags() except Abort, e: raise RepositoryError(e.message)
def _index(self, revision, method): c.pull_request = None c.anchor_url = anchor_url c.ignorews_url = _ignorews_url c.context_url = _context_url c.fulldiff = fulldiff = request.GET.get('fulldiff') #get ranges of revisions if preset rev_range = revision.split('...')[:2] enable_comments = True c.cs_repo = c.db_repo try: if len(rev_range) == 2: enable_comments = False rev_start = rev_range[0] rev_end = rev_range[1] rev_ranges = c.db_repo_scm_instance.get_changesets(start=rev_start, end=rev_end) else: rev_ranges = [c.db_repo_scm_instance.get_changeset(revision)] c.cs_ranges = list(rev_ranges) if not c.cs_ranges: raise RepositoryError('Changeset range returned empty result') except (ChangesetDoesNotExistError, EmptyRepositoryError): log.debug(traceback.format_exc()) msg = _('Such revision does not exist for this repository') h.flash(msg, category='error') raise HTTPNotFound() c.changes = OrderedDict() c.lines_added = 0 # count of lines added c.lines_deleted = 0 # count of lines removes c.changeset_statuses = ChangesetStatus.STATUSES comments = dict() c.statuses = [] c.inline_comments = [] c.inline_cnt = 0 # Iterate over ranges (default changeset view is always one changeset) for changeset in c.cs_ranges: if method == 'show': c.statuses.extend([ChangesetStatusModel().get_status( c.db_repo.repo_id, changeset.raw_id)]) # Changeset comments comments.update((com.comment_id, com) for com in ChangesetCommentsModel() .get_comments(c.db_repo.repo_id, revision=changeset.raw_id)) # Status change comments - mostly from pull requests comments.update((st.comment_id, st.comment) for st in ChangesetStatusModel() .get_statuses(c.db_repo.repo_id, changeset.raw_id, with_revisions=True) if st.comment_id is not None) inlines = ChangesetCommentsModel() \ .get_inline_comments(c.db_repo.repo_id, revision=changeset.raw_id) c.inline_comments.extend(inlines) cs2 = changeset.raw_id cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset().raw_id context_lcl = get_line_ctx('', request.GET) ign_whitespace_lcl = get_ignore_ws('', request.GET) _diff = c.db_repo_scm_instance.get_diff(cs1, cs2, ignore_whitespace=ign_whitespace_lcl, context=context_lcl) diff_limit = self.cut_off_limit if not fulldiff else None diff_processor = diffs.DiffProcessor(_diff, vcs=c.db_repo_scm_instance.alias, format='gitdiff', diff_limit=diff_limit) file_diff_data = [] if method == 'show': _parsed = diff_processor.prepare() c.limited_diff = False if isinstance(_parsed, LimitedDiffContainer): c.limited_diff = True for f in _parsed: st = f['stats'] c.lines_added += st['added'] c.lines_deleted += st['deleted'] filename = f['filename'] fid = h.FID(changeset.raw_id, filename) url_fid = h.FID('', filename) diff = diff_processor.as_html(enable_comments=enable_comments, parsed_lines=[f]) file_diff_data.append((fid, url_fid, f['operation'], f['old_filename'], filename, diff, st)) else: # downloads/raw we only need RAW diff nothing else diff = diff_processor.as_raw() file_diff_data.append(('', None, None, None, diff, None)) c.changes[changeset.raw_id] = (cs1, cs2, file_diff_data) #sort comments in creation order c.comments = [com for com_id, com in sorted(comments.items())] # count inline comments for __, lines in c.inline_comments: for comments in lines.values(): c.inline_cnt += len(comments) if len(c.cs_ranges) == 1: c.changeset = c.cs_ranges[0] c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id for x in c.changeset.parents]) if method == 'download': response.content_type = 'text/plain' response.content_disposition = 'attachment; filename=%s.diff' \ % revision[:12] return diff elif method == 'patch': response.content_type = 'text/plain' c.diff = safe_unicode(diff) return render('changeset/patch_changeset.html') elif method == 'raw': response.content_type = 'text/plain' return diff elif method == 'show': self.__load_data() if len(c.cs_ranges) == 1: return render('changeset/changeset.html') else: c.cs_ranges_org = None c.cs_comments = {} revs = [ctx.revision for ctx in reversed(c.cs_ranges)] c.jsdata = graph_data(c.db_repo_scm_instance, revs) return render('changeset/changeset_range.html')
def get_changesets(self, start=None, end=None, start_date=None, end_date=None, branch_name=None, reverse=False, max_revisions=None): """ Returns iterator of ``GitChangeset`` objects from start to end (both are inclusive), in ascending date order (unless ``reverse`` is set). :param start: changeset ID, as str; first returned changeset :param end: changeset ID, as str; last returned changeset :param start_date: if specified, changesets with commit date less than ``start_date`` would be filtered out from returned set :param end_date: if specified, changesets with commit date greater than ``end_date`` would be filtered out from returned set :param branch_name: if specified, changesets not reachable from given branch would be filtered out from returned set :param reverse: if ``True``, returned generator would be reversed (meaning that returned changesets would have descending date order) :raise BranchDoesNotExistError: If given ``branch_name`` does not exist. :raise ChangesetDoesNotExistError: If changeset for given ``start`` or ``end`` could not be found. """ if branch_name and branch_name not in self.branches: raise BranchDoesNotExistError("Branch '%s' not found" % branch_name) # actually we should check now if it's not an empty repo to not spaw # subprocess commands if self._empty: raise EmptyRepositoryError("There are no changesets yet") # %H at format means (full) commit hash, initial hashes are retrieved # in ascending date order cmd = ['log', '--date-order', '--reverse', '--pretty=format:%H'] if max_revisions: cmd += ['--max-count=%s' % max_revisions] if start_date: cmd += ['--since', start_date.strftime('%m/%d/%y %H:%M:%S')] if end_date: cmd += ['--until', end_date.strftime('%m/%d/%y %H:%M:%S')] if branch_name: cmd.append(branch_name) else: cmd.append(settings.GIT_REV_FILTER) revs = self.run_git_command(cmd).splitlines() start_pos = 0 end_pos = len(revs) if start: _start = self._get_revision(start) try: start_pos = revs.index(_start) except ValueError: pass if end is not None: _end = self._get_revision(end) try: end_pos = revs.index(_end) except ValueError: pass if None not in [start, end] and start_pos > end_pos: raise RepositoryError('start cannot be after end') if end_pos is not None: end_pos += 1 revs = revs[start_pos:end_pos] if reverse: revs.reverse() return CollectionGenerator(self, revs)
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 derived :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) if not isinstance(message, str): raise RepositoryError('message must be a str - got %r' % type(message)) if not isinstance(author, str): raise RepositoryError('author must be a str - got %r' % type(author)) from .repository import MercurialRepository if branch is None: branch = MercurialRepository.DEFAULT_BRANCH_NAME kwargs[b'branch'] = safe_bytes(branch) def filectxfn(_repo, memctx, bytes_path): """ Callback from Mercurial, returning ctx to commit for the given path. """ path = safe_str(bytes_path) # check if this path is removed if path in (node.path for node in self.removed): return None # check if this path is added for node in self.added: if node.path == path: return mercurial.context.memfilectx( _repo, memctx, path=bytes_path, data=node.content, islink=False, isexec=node.is_executable, copysource=False) # or changed for node in self.changed: if node.path == path: return mercurial.context.memfilectx( _repo, memctx, path=bytes_path, data=node.content, islink=False, isexec=node.is_executable, copysource=False) raise RepositoryError("Given path haven't been marked as added, " "changed or removed (%s)" % path) parents = [None, None] for i, parent in enumerate(self.parents): if parent is not None: parents[i] = parent._ctx.node() if date and isinstance(date, datetime.datetime): date = safe_bytes(date.strftime('%a, %d %b %Y %H:%M:%S')) commit_ctx = mercurial.context.memctx( repo=self.repository._repo, parents=parents, text=b'', files=[safe_bytes(x) for x in self.get_paths()], filectxfn=filectxfn, user=safe_bytes(author), date=date, extra=kwargs) # injecting given _repo params commit_ctx._text = safe_bytes(message) commit_ctx._user = safe_bytes(author) commit_ctx._date = date # TODO: Catch exceptions! n = self.repository._repo.commitctx(commit_ctx) # Returns mercurial node self._commit_ctx = commit_ctx # For reference # Update vcs repository object & recreate mercurial _repo # new_ctx = self.repository._repo[node] # new_tip = ascii_str(self.repository.get_changeset(new_ctx.hex())) self.repository.revisions.append(ascii_str(mercurial.node.hex(n))) self._repo = self.repository._get_repo(create=False) self.repository.branches = self.repository._get_branches() 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 derived :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 MercurialRepository if not isinstance(message, unicode) or not isinstance(author, unicode): raise RepositoryError('Given message and author needs to be ' 'an <unicode> instance got %r & %r instead' % (type(message), type(author))) if branch is None: branch = MercurialRepository.DEFAULT_BRANCH_NAME kwargs['branch'] = branch def filectxfn(_repo, memctx, path): """ Marks given path as added/changed/removed in a given _repo. This is for internal mercurial commit function. """ # check if this path is removed if path in (node.path for node in self.removed): if getattr(memctx, '_returnnoneformissingfiles', False): return None else: # (hg < 3.2) Raising exception is the way to mark node for # removal raise IOError(errno.ENOENT, '%s is deleted' % path) # check if this path is added for node in self.added: if node.path == path: return memfilectx( _repo, path=node.path, data=(node.content.encode('utf8') if not node.is_binary else node.content), islink=False, isexec=node.is_executable, copied=False) # or changed for node in self.changed: if node.path == path: return memfilectx( _repo, path=node.path, data=(node.content.encode('utf8') if not node.is_binary else node.content), islink=False, isexec=node.is_executable, copied=False) raise RepositoryError("Given path haven't been marked as added," "changed or removed (%s)" % path) parents = [None, None] for i, parent in enumerate(self.parents): if parent is not None: parents[i] = parent._ctx.node() if date and isinstance(date, datetime.datetime): date = date.strftime('%a, %d %b %Y %H:%M:%S') commit_ctx = memctx(repo=self.repository._repo, parents=parents, text='', files=self.get_paths(), filectxfn=filectxfn, user=author, date=date, extra=kwargs) loc = lambda u: tolocal(u.encode('utf-8')) # injecting given _repo params commit_ctx._text = loc(message) commit_ctx._user = loc(author) commit_ctx._date = date # TODO: Catch exceptions! n = self.repository._repo.commitctx(commit_ctx) # Returns mercurial node self._commit_ctx = commit_ctx # For reference # Update vcs repository object & recreate mercurial _repo # new_ctx = self.repository._repo[node] # new_tip = self.repository.get_changeset(new_ctx.hex()) new_id = hex(n) self.repository.revisions.append(new_id) self._repo = self.repository._get_repo(create=False) self.repository.branches = self.repository._get_branches() tip = self.repository.get_changeset() self.reset() return tip