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.mkdir(self.path) if bare: return Repo.init_bare(self.path) else: return Repo.init(self.path) else: return self._repo except (NotGitRepository, OSError), 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 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 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): # Raising exception is a 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( 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( 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 pull(self, url): """ Tries to pull changes from external location. """ url = self._get_url(url) try: pull(self.baseui, self._repo, url) except Abort, err: # Propagate error but with vcs's type raise RepositoryError(str(err))
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 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(): raise BranchDoesNotExistError('Branch %s not found in' ' this repository' % branch_name) if end_pos is not None: end_pos += 1 #filter branches filter_ = [] if branch_name: filter_.append('branch("%s")' % (branch_name)) if start_date: filter_.append('date(">%s")' % start_date) if end_date: filter_.append('date("<%s")' % end_date) if filter_: revisions = scmutil.revrange(self._repo, [" and ".join(filter_)]) else: revisions = self.revisions revs = revisions[start_pos:end_pos] if reverse: revs = reversed(revs) return CollectionGenerator(self, revs)
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 _str_cmd = False if isinstance(cmd, basestring): cmd = [cmd] _str_cmd = True 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 if _str_cmd: cmd = ' '.join(cmd) try: _opts = dict( env=gitenv, shell=False, ) _opts.update(opts) p = subprocessio.SubprocessIOChunker(cmd, **_opts) except (EnvironmentError, OSError), err: tb_err = ("Couldn't run git command (%s).\n" "Original error was:%s\n" % (cmd, err)) if safe_call: return '', err else: raise RepositoryError(tb_err)
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, e: raise RepositoryError(e.strerror)
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 = str(self._get_url(src_url)) opts = {} if not update_after_clone: opts.update({'noupdate': True}) try: MercurialRepository._check_url(url) clone(self.baseui, url, self.path, **opts) # except urllib2.URLError: # raise Abort("Got HTTP 404 error") except Exception: raise # Don't try to create if we've already cloned repo create = False return localrepository(self.baseui, self.path, create=create) except (Abort, RepoError), 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 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 as e: raise RepositoryError(e.message)
def __init__(self, repository, revision): self._stat_modes = {} self.repository = repository try: commit = self.repository._repo[revision] if isinstance(commit, objects.Tag): revision = 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 = revision self.id = self.raw_id self.short_id = self.raw_id[:12] self._commit = commit 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(revision) self.nodes = {} self._paths = {}
def get_changesets(self, start=None, end=None, start_date=None, end_date=None, branch_name=None, reverse=False): """ 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) # %H at format means (full) commit hash, initial hashes are retrieved # in ascending date order cmd_template = 'log --date-order --reverse --pretty=format:"%H"' cmd_params = {} if start_date: cmd_template += ' --since "$since"' cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S') if end_date: cmd_template += ' --until "$until"' cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S') if branch_name: cmd_template += ' $branch_name' cmd_params['branch_name'] = branch_name else: rev_filter = _git_path = settings.GIT_REV_FILTER cmd_template += ' %s' % (rev_filter) cmd = string.Template(cmd_template).safe_substitute(**cmd_params) revs = self.run_git_command(cmd)[0].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 = reversed(revs) 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 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 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): # Raising exception is a 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( 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( 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.ctime() 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