def strip_outgoing(self):
        from . import findoutgoing

        if HgVersion(util.version()) >= HgVersion('2.1'):
            remoterepo = hg.peer(self.repo, self.opts, self.rbrepo.path)
        else:
            remoterepo = hg.repository(self.ui, self.rbrepo.path)

        out = findoutgoing(self.repo, remoterepo)
        if not out:
            return

        cl = self.repo.changelog
        revs = set([cl.rev(r) for r in out])
        if HgVersion(util.version()) >= HgVersion('2.3'):
            descendants = set(cl.descendants(revs))
        else:
            descendants = set(cl.descendants(*revs))

        roots = revs.difference(descendants)

        roots = list(roots)
        roots.sort()
        roots.reverse()
        self.ui.status("Stripping local revisions %s\n" % roots)

        for node in roots:
            self.ui.note("Stripping revision %s...\n" % node)
            self.ui.pushbuffer()
            try:
                repair.strip(self.ui, self.repo, cl.node(node), backup='none')
            finally:
                self.ui.popbuffer()
Пример #2
0
 def get_repository(self, type, dir, params):
     """Return a `MercurialRepository`"""
     if not self._version:
         try:
             from mercurial.version import get_version
             self._version = get_version()
         except ImportError: # gone in Mercurial 1.2 (hg:9626819b2e3d)
             from mercurial.util import version
             self._version = version()
         # development version assumed to be always the ''newest'' one,
         # i.e. old development version won't be supported
         self._version_info = (999, 0, 0) 
         m = re.match(r'(\d+)\.(\d+)(?:\.(\d+))?', self._version or '')
         if m:
             self._version_info = tuple([int(n or 0) for n in m.groups()])
         self.env.systeminfo.append(('Mercurial', self._version))
     options = {}
     for key, val in self.config.options(type):
         options[key] = val
     options.update(params)
     if not self.ui:
         self._setup_ui(options.get('hgrc'))
     repos = MercurialRepository(dir, options, self.log, self.ui)
     repos.version_info = self._version_info
     return repos
Пример #3
0
    def toggleAmend(self, opts):
        """Toggle the amend flag.

        When the amend flag is set, a commit will modify the most recently
        committed changeset, instead of creating a new changeset.  Otherwise, a
        new changeset will be created (the normal commit behavior).

        """
        try:
            ver = float(util.version()[:3])
        except:
            # not sure if needed: for earlier versions that may not have
            # util.vesrion()...
            ver = 1
        if ver < 2.19:
            msg = ("The amend option is unavailable with hg versions < 2.2\n\n"
                   "Press any key to continue.")
        elif opts.get('amend') is None:
            opts['amend'] = True
            msg = ("Amend option is turned on -- commiting the currently "
                   "selected changes will not create a new changeset, but "
                   "instead update the most recently committed changeset.\n\n"
                   "Press any key to continue.")
        elif opts.get('amend') is True:
            opts['amend'] = None
            msg = ("Amend option is turned off -- commiting the currently "
                   "selected changes will create a new changeset.\n\n"
                   "Press any key to continue.")

        self.confirmationWindow(msg)
Пример #4
0
    def versiontuple(v=None, n=4):
        if not v:
            v = util.version()
        parts = v.split('+', 1)
        if len(parts) == 1:
            vparts, extra = parts[0], None
        else:
            vparts, extra = parts

        vints = []
        for i in vparts.split('.'):
            try:
                vints.append(int(i))
            except ValueError:
                break
        # (3, 6) -> (3, 6, None)
        while len(vints) < 3:
            vints.append(None)

        if n == 2:
            return (vints[0], vints[1])
        if n == 3:
            return (vints[0], vints[1], vints[2])
        if n == 4:
            return (vints[0], vints[1], vints[2], extra)
Пример #5
0
def new_commit(orig_commit, ui, repo, *pats, **opts):
    smelly_count = 0
    if util.version() >= '1.9':
        match = utilmodule.match(repo[None], pats, opts)
    else:
        match = utilmodule.match(repo, pats, opts)

    revs = utilmodule.revpair(repo, None)
    changes = repo.status(*revs, match=match)
    change_message = opts['message']
    if changes[1]:
        # check if any added files would be ignored
        for fn in changes[1]:
            if repo.dirstate._ignore(fn):
                ui.warn('File %r added, but it would be ignored.\n' % fn)
                smelly_count += 1

    for rex, reason in SMELLY_MESSAGE_STUFF:
        if rex.match(change_message):
            ui.warn('Smelly change message (%s)\n' % reason)
            smelly_count += 1

    diff = patch.diff(repo, *revs, match=match)
    smellies = []
    for chunk in diff:
        chunklines = chunk.splitlines(True)
        indexline = 0
        hunkstart = 0
        for i, line in enumerate(chunklines):
            if line.startswith('diff'):
                indexline = i
                # new file: collect all smelly patterns for it
                filename = line.split()[-1]
                smellies = []
                for pat, smelly in SMELLY_STUFF.iteritems():
                    if not fnmatch.fnmatch(filename, pat):
                        continue
                    smellies.extend(smelly)
            elif line.startswith('@@'):
                hunkstart = i
            elif line.startswith('+'):
                for rex, reason in smellies:
                    if rex.search(line):
                        ui.warn('Smelly change (%s):\n' % reason)
                        diff = chunklines[indexline:indexline+3] + \
                               chunklines[hunkstart:i+4]
                        write_colored(ui, diff)
                        smelly_count += 1
                        break
                else:
                    continue
                break
    if smelly_count:
        if not ui.prompt('Found %d smelly change%s. Continue (y/N)?' %
                         (smelly_count, smelly_count != 1 and 's' or ''),
                         default='n').lower().startswith('y'):
            return smelly_count
    return orig_commit(ui, repo, *pats, **opts)
Пример #6
0
 def __init__(self, repo, url):
     ui = repo.ui
     self.ui = ui
     baseurl, authinfo = url.authinfo()
     self.baseurl = baseurl.rstrip('/')
     useragent = repo.ui.config('experimental', 'lfs.user-agent')
     if not useragent:
         useragent = 'git-lfs/2.3.4 (Mercurial %s)' % util.version()
     self.urlopener = urlmod.opener(ui, authinfo, useragent)
     self.retry = ui.configint('lfs', 'retry')
Пример #7
0
    def __init__(self, repoPath, local_site):
        from mercurial import hg, ui, error

        # We've encountered problems getting the Mercurial version number.
        # Originally, we imported 'version' from mercurial.__version__,
        # which would sometimes return None.
        #
        # We are now trying to go through their version() function, if
        # available. That is likely the most reliable.
        try:
            from mercurial.util import version
            hg_version = version()
        except ImportError:
            # If version() wasn't available, we'll try to import __version__
            # ourselves, and then get 'version' from that.
            try:
                from mercurial import __version__
                hg_version = __version__.version
            except ImportError:
                # If that failed, we'll hard-code an empty string. This will
                # trigger the "<= 1.2" case below.
                hg_version = ''

        # If something gave us None, convert it to an empty string so
        # parse_version can accept it.
        if hg_version is None:
            hg_version = ''

        if parse_version(hg_version) <= parse_version("1.2"):
            hg_ui = ui.ui(interactive=False)
        else:
            hg_ui = ui.ui()
            hg_ui.setconfig('ui', 'interactive', 'off')

        # Check whether ssh is configured for mercurial. Assume that any
        # configured ssh is set up correctly for this repository.
        hg_ssh = hg_ui.config('ui', 'ssh')

        if not hg_ssh:
            logging.debug('Using rbssh for mercurial')
            hg_ui.setconfig('ui', 'ssh', 'rbssh --rb-local-site=%s'
                            % local_site)
        else:
            logging.debug('Found configured ssh for mercurial: %s' % hg_ssh)

        try:
            self.repo = hg.repository(hg_ui, path=repoPath)
        except error.RepoError, e:
            logging.error('Error connecting to Mercurial repository %s: %s'
                          % (repoPath, e))
            raise RepositoryNotFoundError
Пример #8
0
    def __init__(self, repoPath, local_site):
        from mercurial import hg, ui, error

        # We've encountered problems getting the Mercurial version number.
        # Originally, we imported 'version' from mercurial.__version__,
        # which would sometimes return None.
        #
        # We are now trying to go through their version() function, if
        # available. That is likely the most reliable.
        try:
            from mercurial.util import version
            hg_version = version()
        except ImportError:
            # If version() wasn't available, we'll try to import __version__
            # ourselves, and then get 'version' from that.
            try:
                from mercurial import __version__
                hg_version = __version__.version
            except ImportError:
                # If that failed, we'll hard-code an empty string. This will
                # trigger the "<= 1.2" case below.
                hg_version = ''

        # If something gave us None, convert it to an empty string so
        # parse_version can accept it.
        if hg_version is None:
            hg_version = ''

        if parse_version(hg_version) <= parse_version("1.2"):
            hg_ui = ui.ui(interactive=False)
        else:
            hg_ui = ui.ui()
            hg_ui.setconfig('ui', 'interactive', 'off')

        # Check whether ssh is configured for mercurial. Assume that any
        # configured ssh is set up correctly for this repository.
        hg_ssh = hg_ui.config('ui', 'ssh')

        if not hg_ssh:
            logging.debug('Using rbssh for mercurial')
            hg_ui.setconfig('ui', 'ssh',
                            'rbssh --rb-local-site=%s' % local_site)
        else:
            logging.debug('Found configured ssh for mercurial: %s' % hg_ssh)

        try:
            self.repo = hg.repository(hg_ui, path=repoPath)
        except error.RepoError, e:
            logging.error('Error connecting to Mercurial repository %s: %s' %
                          (repoPath, e))
            raise RepositoryNotFoundError
Пример #9
0
def remoteparent(ui, repo, opts, ctx, upstream=None):
    remotepath = expandpath(ui, upstream)
    if HgVersion(util.version()) >= HgVersion('2.1'):
        remoterepo = hg.peer(repo, opts, remotepath)
    else:
        remoterepo = hg.repository(ui, remotepath)
    
    out = findoutgoing(repo, remoterepo)
    
    for o in out:
        orev = repo[o]
        a, b, c = repo.changelog.nodesbetween([orev.node()], [ctx.node()])
        if a:
            return orev.parents()[0]
Пример #10
0
def robustcheckout(ui,
                   url,
                   dest,
                   upstream=None,
                   revision=None,
                   branch=None,
                   purge=False,
                   sharebase=None,
                   networkattempts=None):
    """Ensure a working copy has the specified revision checked out."""
    if not revision and not branch:
        raise error.Abort('must specify one of --revision or --branch')

    if revision and branch:
        raise error.Abort('cannot specify both --revision and --branch')

    # Require revision to look like a SHA-1.
    if revision:
        if len(revision) < 12 or len(revision) > 40 or not re.match(
                '^[a-f0-9]+$', revision):
            raise error.Abort('--revision must be a SHA-1 fragment 12-40 '
                              'characters long')

    sharebase = sharebase or ui.config('share', 'pool')
    if not sharebase:
        raise error.Abort(
            'share base directory not defined; refusing to operate',
            hint='define share.pool config option or pass --sharebase')

    ui.warn('(using Mercurial %s)\n' % util.version())

    # worker.backgroundclose only makes things faster if running anti-virus,
    # which our automation doesn't. Disable it.
    ui.setconfig('worker', 'backgroundclose', False)

    # By default the progress bar starts after 3s and updates every 0.1s. We
    # change this so it shows and updates every 1.0s.
    # We also tell progress to assume a TTY is present so updates are printed
    # even if there is no known TTY.
    # We make the config change here instead of in a config file because
    # otherwise we're at the whim of whatever configs are used in automation.
    ui.setconfig('progress', 'delay', 1.0)
    ui.setconfig('progress', 'refresh', 1.0)
    ui.setconfig('progress', 'assume-tty', True)

    sharebase = os.path.realpath(sharebase)

    return _docheckout(ui, url, dest, upstream, revision, branch, purge,
                       sharebase, networkattempts)
Пример #11
0
def _checkhgversion(ui, hgversion):
    if hgversion >= OLDEST_NON_LEGACY_VERSION:
        return

    ui.warn(LEGACY_MERCURIAL_MESSAGE % util.version())
    ui.warn('\n')

    if os.name == 'nt':
        ui.warn('Please upgrade to the latest MozillaBuild to upgrade '
                'your Mercurial install.\n\n')
    else:
        ui.warn('Please run `mach bootstrap` to upgrade your Mercurial '
                'install.\n\n')

    if uipromptchoice(ui, 'Would you like to continue using an old Mercurial version (Yn)? $$ &Yes $$ &No'):
        return 1
Пример #12
0
def findoutgoing(repo, remoterepo):
    # The method for doing this has changed a few times...
    try:
        from mercurial import discovery
    except ImportError:
        # Must be earlier than 1.6
        return repo.findoutgoing(remoterepo)

    try:
        if LooseVersion(util.version()) >= LooseVersion('2.1'):
            outgoing = discovery.findcommonoutgoing(repo, remoterepo)
            return outgoing.missing
        common, outheads = discovery.findcommonoutgoing(repo, remoterepo)
        return repo.changelog.findmissing(common=common, heads=outheads)
    except AttributeError:
        # Must be earlier than 1.9
        return discovery.findoutgoing(repo, remoterepo)
Пример #13
0
def findoutgoing(repo, remoterepo):
    # The method for doing this has changed a few times...
    try:
        from mercurial import discovery
    except ImportError:
        # Must be earlier than 1.6
        return repo.findoutgoing(remoterepo)

    try:
        if LooseVersion(util.version()) >= LooseVersion('2.1'):
            outgoing = discovery.findcommonoutgoing(repo, remoterepo)
            return outgoing.missing
        common, outheads = discovery.findcommonoutgoing(repo, remoterepo)
        return repo.changelog.findmissing(common=common, heads=outheads)
    except AttributeError:
        # Must be earlier than 1.9
        return discovery.findoutgoing(repo, remoterepo)
Пример #14
0
def patchbomb(ui, repo, *revs, **opts):
    '''send changesets by email

    By default, diffs are sent in the format generated by
    :hg:`export`, one per message. The series starts with a "[PATCH 0
    of N]" introduction, which describes the series as a whole.

    Each patch email has a Subject line of "[PATCH M of N] ...", using
    the first line of the changeset description as the subject text.
    The message contains two or three parts. First, the changeset
    description.

    With the -d/--diffstat option, if the diffstat program is
    installed, the result of running diffstat on the patch is inserted.

    Finally, the patch itself, as generated by :hg:`export`.

    With the -d/--diffstat or --confirm options, you will be presented
    with a final summary of all messages and asked for confirmation before
    the messages are sent.

    By default the patch is included as text in the email body for
    easy reviewing. Using the -a/--attach option will instead create
    an attachment for the patch. With -i/--inline an inline attachment
    will be created. You can include a patch both as text in the email
    body and as a regular or an inline attachment by combining the
    -a/--attach or -i/--inline with the --body option.

    With -o/--outgoing, emails will be generated for patches not found
    in the destination repository (or only those which are ancestors
    of the specified revisions if any are provided)

    With -b/--bundle, changesets are selected as for --outgoing, but a
    single email containing a binary Mercurial bundle as an attachment
    will be sent.

    With -m/--mbox, instead of previewing each patchbomb message in a
    pager or sending the messages directly, it will create a UNIX
    mailbox file with the patch emails. This mailbox file can be
    previewed with any mail user agent which supports UNIX mbox
    files.

    With -n/--test, all steps will run, but mail will not be sent.
    You will be prompted for an email recipient address, a subject and
    an introductory message describing the patches of your patchbomb.
    Then when all is done, patchbomb messages are displayed. If the
    PAGER environment variable is set, your pager will be fired up once
    for each patchbomb message, so you can verify everything is alright.

    In case email sending fails, you will find a backup of your series
    introductory message in ``.hg/last-email.txt``.

    The default behavior of this command can be customized through
    configuration. (See :hg:`help patchbomb` for details)

    Examples::

      hg email -r 3000          # send patch 3000 only
      hg email -r 3000 -r 3001  # send patches 3000 and 3001
      hg email -r 3000:3005     # send patches 3000 through 3005
      hg email 3000             # send patch 3000 (deprecated)

      hg email -o               # send all patches not in default
      hg email -o DEST          # send all patches not in DEST
      hg email -o -r 3000       # send all ancestors of 3000 not in default
      hg email -o -r 3000 DEST  # send all ancestors of 3000 not in DEST

      hg email -b               # send bundle of all patches not in default
      hg email -b DEST          # send bundle of all patches not in DEST
      hg email -b -r 3000       # bundle of all ancestors of 3000 not in default
      hg email -b -r 3000 DEST  # bundle of all ancestors of 3000 not in DEST

      hg email -o -m mbox &&    # generate an mbox file...
        mutt -R -f mbox         # ... and view it with mutt
      hg email -o -m mbox &&    # generate an mbox file ...
        formail -s sendmail \\   # ... and use formail to send from the mbox
          -bm -t < mbox         # ... using sendmail

    Before using this command, you will need to enable email in your
    hgrc. See the [email] section in hgrc(5) for details.
    '''

    _charsets = mail._charsets(ui)

    bundle = opts.get('bundle')
    date = opts.get('date')
    mbox = opts.get('mbox')
    outgoing = opts.get('outgoing')
    rev = opts.get('rev')
    # internal option used by pbranches
    patches = opts.get('patches')

    if not (opts.get('test') or mbox):
        # really sending
        mail.validateconfig(ui)

    if not (revs or rev or outgoing or bundle or patches):
        raise util.Abort(_('specify at least one changeset with -r or -o'))

    if outgoing and bundle:
        raise util.Abort(
            _("--outgoing mode always on with --bundle;"
              " do not re-specify --outgoing"))

    if outgoing or bundle:
        if len(revs) > 1:
            raise util.Abort(_("too many destinations"))
        if revs:
            dest = revs[0]
        else:
            dest = None
        revs = []

    if rev:
        if revs:
            raise util.Abort(_('use only one form to specify the revision'))
        revs = rev

    if outgoing:
        revs = _getoutgoing(repo, dest, rev)
    if bundle:
        opts['revs'] = revs

    # start
    if date:
        start_time = util.parsedate(date)
    else:
        start_time = util.makedate()

    def genmsgid(id):
        return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())

    sender = (opts.get('from') or ui.config('email', 'from')
              or ui.config('patchbomb', 'from')
              or prompt(ui, 'From', ui.username()))

    if patches:
        msgs = _getpatchmsgs(repo, sender, patches, opts.get('patchnames'),
                             **opts)
    elif bundle:
        bundledata = _getbundle(repo, dest, **opts)
        bundleopts = opts.copy()
        bundleopts.pop('bundle', None)  # already processed
        msgs = _getbundlemsgs(repo, sender, bundledata, **bundleopts)
    else:
        _patches = list(_getpatches(repo, revs, **opts))
        msgs = _getpatchmsgs(repo, sender, _patches, **opts)

    showaddrs = []

    def getaddrs(header, ask=False, default=None):
        configkey = header.lower()
        opt = header.replace('-', '_').lower()
        addrs = opts.get(opt)
        if addrs:
            showaddrs.append('%s: %s' % (header, ', '.join(addrs)))
            return mail.addrlistencode(ui, addrs, _charsets, opts.get('test'))

        # not on the command line: fallback to config and then maybe ask
        addr = (ui.config('email', configkey)
                or ui.config('patchbomb', configkey) or '')
        if not addr and ask:
            addr = prompt(ui, header, default=default)
        if addr:
            showaddrs.append('%s: %s' % (header, addr))
            return mail.addrlistencode(ui, [addr], _charsets, opts.get('test'))
        else:
            return default

    to = getaddrs('To', ask=True)
    if not to:
        # we can get here in non-interactive mode
        raise util.Abort(_('no recipient addresses provided'))
    cc = getaddrs('Cc', ask=True, default='') or []
    bcc = getaddrs('Bcc') or []
    replyto = getaddrs('Reply-To')

    confirm = ui.configbool('patchbomb', 'confirm')
    confirm |= bool(opts.get('diffstat') or opts.get('confirm'))

    if confirm:
        ui.write(_('\nFinal summary:\n\n'), label='patchbomb.finalsummary')
        ui.write(('From: %s\n' % sender), label='patchbomb.from')
        for addr in showaddrs:
            ui.write('%s\n' % addr, label='patchbomb.to')
        for m, subj, ds in msgs:
            ui.write(('Subject: %s\n' % subj), label='patchbomb.subject')
            if ds:
                ui.write(ds, label='patchbomb.diffstats')
        ui.write('\n')
        if ui.promptchoice(
                _('are you sure you want to send (yn)?'
                  '$$ &Yes $$ &No')):
            raise util.Abort(_('patchbomb canceled'))

    ui.write('\n')

    parent = opts.get('in_reply_to') or None
    # angle brackets may be omitted, they're not semantically part of the msg-id
    if parent is not None:
        if not parent.startswith('<'):
            parent = '<' + parent
        if not parent.endswith('>'):
            parent += '>'

    sender_addr = email.Utils.parseaddr(sender)[1]
    sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
    sendmail = None
    firstpatch = None
    for i, (m, subj, ds) in enumerate(msgs):
        try:
            m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
            if not firstpatch:
                firstpatch = m['Message-Id']
            m['X-Mercurial-Series-Id'] = firstpatch
        except TypeError:
            m['Message-Id'] = genmsgid('patchbomb')
        if parent:
            m['In-Reply-To'] = parent
            m['References'] = parent
        if not parent or 'X-Mercurial-Node' not in m:
            parent = m['Message-Id']

        m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
        m['Date'] = email.Utils.formatdate(start_time[0], localtime=True)

        start_time = (start_time[0] + 1, start_time[1])
        m['From'] = sender
        m['To'] = ', '.join(to)
        if cc:
            m['Cc'] = ', '.join(cc)
        if bcc:
            m['Bcc'] = ', '.join(bcc)
        if replyto:
            m['Reply-To'] = ', '.join(replyto)
        if opts.get('test'):
            ui.status(_('displaying '), subj, ' ...\n')
            ui.flush()
            if 'PAGER' in os.environ and not ui.plain():
                fp = util.popen(os.environ['PAGER'], 'w')
            else:
                fp = ui
            generator = email.Generator.Generator(fp, mangle_from_=False)
            try:
                generator.flatten(m, 0)
                fp.write('\n')
            except IOError, inst:
                if inst.errno != errno.EPIPE:
                    raise
            if fp is not ui:
                fp.close()
        else:
            if not sendmail:
                verifycert = ui.config('smtp', 'verifycert')
                if opts.get('insecure'):
                    ui.setconfig('smtp', 'verifycert', 'loose', 'patchbomb')
                try:
                    sendmail = mail.connect(ui, mbox=mbox)
                finally:
                    ui.setconfig('smtp', 'verifycert', verifycert, 'patchbomb')
            ui.status(_('sending '), subj, ' ...\n')
            ui.progress(_('sending'), i, item=subj, total=len(msgs))
            if not mbox:
                # Exim does not remove the Bcc field
                del m['Bcc']
            fp = cStringIO.StringIO()
            generator = email.Generator.Generator(fp, mangle_from_=False)
            generator.flatten(m, 0)
            sendmail(sender_addr, to + bcc + cc, fp.getvalue())
Пример #15
0
def patchbomb(ui, repo, *revs, **opts):
    '''send changesets by email

    By default, diffs are sent in the format generated by
    :hg:`export`, one per message. The series starts with a "[PATCH 0
    of N]" introduction, which describes the series as a whole.

    Each patch email has a Subject line of "[PATCH M of N] ...", using
    the first line of the changeset description as the subject text.
    The message contains two or three parts. First, the changeset
    description.

    With the -d/--diffstat option, if the diffstat program is
    installed, the result of running diffstat on the patch is inserted.

    Finally, the patch itself, as generated by :hg:`export`.

    With the -d/--diffstat or --confirm options, you will be presented
    with a final summary of all messages and asked for confirmation before
    the messages are sent.

    By default the patch is included as text in the email body for
    easy reviewing. Using the -a/--attach option will instead create
    an attachment for the patch. With -i/--inline an inline attachment
    will be created. You can include a patch both as text in the email
    body and as a regular or an inline attachment by combining the
    -a/--attach or -i/--inline with the --body option.

    With -o/--outgoing, emails will be generated for patches not found
    in the destination repository (or only those which are ancestors
    of the specified revisions if any are provided)

    With -b/--bundle, changesets are selected as for --outgoing, but a
    single email containing a binary Mercurial bundle as an attachment
    will be sent.

    With -m/--mbox, instead of previewing each patchbomb message in a
    pager or sending the messages directly, it will create a UNIX
    mailbox file with the patch emails. This mailbox file can be
    previewed with any mail user agent which supports UNIX mbox
    files.

    With -n/--test, all steps will run, but mail will not be sent.
    You will be prompted for an email recipient address, a subject and
    an introductory message describing the patches of your patchbomb.
    Then when all is done, patchbomb messages are displayed. If the
    PAGER environment variable is set, your pager will be fired up once
    for each patchbomb message, so you can verify everything is alright.

    In case email sending fails, you will find a backup of your series
    introductory message in ``.hg/last-email.txt``.

    The default behavior of this command can be customized through
    configuration. (See :hg:`help patchbomb` for details)

    Examples::

      hg email -r 3000          # send patch 3000 only
      hg email -r 3000 -r 3001  # send patches 3000 and 3001
      hg email -r 3000:3005     # send patches 3000 through 3005
      hg email 3000             # send patch 3000 (deprecated)

      hg email -o               # send all patches not in default
      hg email -o DEST          # send all patches not in DEST
      hg email -o -r 3000       # send all ancestors of 3000 not in default
      hg email -o -r 3000 DEST  # send all ancestors of 3000 not in DEST

      hg email -b               # send bundle of all patches not in default
      hg email -b DEST          # send bundle of all patches not in DEST
      hg email -b -r 3000       # bundle of all ancestors of 3000 not in default
      hg email -b -r 3000 DEST  # bundle of all ancestors of 3000 not in DEST

      hg email -o -m mbox &&    # generate an mbox file...
        mutt -R -f mbox         # ... and view it with mutt
      hg email -o -m mbox &&    # generate an mbox file ...
        formail -s sendmail \\   # ... and use formail to send from the mbox
          -bm -t < mbox         # ... using sendmail

    Before using this command, you will need to enable email in your
    hgrc. See the [email] section in hgrc(5) for details.
    '''

    _charsets = mail._charsets(ui)

    bundle = opts.get('bundle')
    date = opts.get('date')
    mbox = opts.get('mbox')
    outgoing = opts.get('outgoing')
    rev = opts.get('rev')
    # internal option used by pbranches
    patches = opts.get('patches')

    if not (opts.get('test') or mbox):
        # really sending
        mail.validateconfig(ui)

    if not (revs or rev or outgoing or bundle or patches):
        raise util.Abort(_('specify at least one changeset with -r or -o'))

    if outgoing and bundle:
        raise util.Abort(_("--outgoing mode always on with --bundle;"
                           " do not re-specify --outgoing"))

    if outgoing or bundle:
        if len(revs) > 1:
            raise util.Abort(_("too many destinations"))
        if revs:
            dest = revs[0]
        else:
            dest = None
        revs = []

    if rev:
        if revs:
            raise util.Abort(_('use only one form to specify the revision'))
        revs = rev

    revs = scmutil.revrange(repo, revs)
    if outgoing:
        revs = _getoutgoing(repo, dest, revs)
    if bundle:
        opts['revs'] = [str(r) for r in revs]

    # start
    if date:
        start_time = util.parsedate(date)
    else:
        start_time = util.makedate()

    def genmsgid(id):
        return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())

    sender = (opts.get('from') or ui.config('email', 'from') or
              ui.config('patchbomb', 'from') or
              prompt(ui, 'From', ui.username()))

    if patches:
        msgs = _getpatchmsgs(repo, sender, patches, opts.get('patchnames'),
                             **opts)
    elif bundle:
        bundledata = _getbundle(repo, dest, **opts)
        bundleopts = opts.copy()
        bundleopts.pop('bundle', None)  # already processed
        msgs = _getbundlemsgs(repo, sender, bundledata, **bundleopts)
    else:
        _patches = list(_getpatches(repo, revs, **opts))
        msgs = _getpatchmsgs(repo, sender, _patches, **opts)

    showaddrs = []

    def getaddrs(header, ask=False, default=None):
        configkey = header.lower()
        opt = header.replace('-', '_').lower()
        addrs = opts.get(opt)
        if addrs:
            showaddrs.append('%s: %s' % (header, ', '.join(addrs)))
            return mail.addrlistencode(ui, addrs, _charsets, opts.get('test'))

        # not on the command line: fallback to config and then maybe ask
        addr = (ui.config('email', configkey) or
                ui.config('patchbomb', configkey) or
                '')
        if not addr and ask:
            addr = prompt(ui, header, default=default)
        if addr:
            showaddrs.append('%s: %s' % (header, addr))
            return mail.addrlistencode(ui, [addr], _charsets, opts.get('test'))
        else:
            return default

    to = getaddrs('To', ask=True)
    if not to:
        # we can get here in non-interactive mode
        raise util.Abort(_('no recipient addresses provided'))
    cc = getaddrs('Cc', ask=True, default='') or []
    bcc = getaddrs('Bcc') or []
    replyto = getaddrs('Reply-To')

    confirm = ui.configbool('patchbomb', 'confirm')
    confirm |= bool(opts.get('diffstat') or opts.get('confirm'))

    if confirm:
        ui.write(_('\nFinal summary:\n\n'), label='patchbomb.finalsummary')
        ui.write(('From: %s\n' % sender), label='patchbomb.from')
        for addr in showaddrs:
            ui.write('%s\n' % addr, label='patchbomb.to')
        for m, subj, ds in msgs:
            ui.write(('Subject: %s\n' % subj), label='patchbomb.subject')
            if ds:
                ui.write(ds, label='patchbomb.diffstats')
        ui.write('\n')
        if ui.promptchoice(_('are you sure you want to send (yn)?'
                             '$$ &Yes $$ &No')):
            raise util.Abort(_('patchbomb canceled'))

    ui.write('\n')

    parent = opts.get('in_reply_to') or None
    # angle brackets may be omitted, they're not semantically part of the msg-id
    if parent is not None:
        if not parent.startswith('<'):
            parent = '<' + parent
        if not parent.endswith('>'):
            parent += '>'

    sender_addr = email.Utils.parseaddr(sender)[1]
    sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
    sendmail = None
    firstpatch = None
    for i, (m, subj, ds) in enumerate(msgs):
        try:
            m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
            if not firstpatch:
                firstpatch = m['Message-Id']
            m['X-Mercurial-Series-Id'] = firstpatch
        except TypeError:
            m['Message-Id'] = genmsgid('patchbomb')
        if parent:
            m['In-Reply-To'] = parent
            m['References'] = parent
        if not parent or 'X-Mercurial-Node' not in m:
            parent = m['Message-Id']

        m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
        m['Date'] = email.Utils.formatdate(start_time[0], localtime=True)

        start_time = (start_time[0] + 1, start_time[1])
        m['From'] = sender
        m['To'] = ', '.join(to)
        if cc:
            m['Cc']  = ', '.join(cc)
        if bcc:
            m['Bcc'] = ', '.join(bcc)
        if replyto:
            m['Reply-To'] = ', '.join(replyto)
        if opts.get('test'):
            ui.status(_('displaying '), subj, ' ...\n')
            ui.flush()
            if 'PAGER' in os.environ and not ui.plain():
                fp = util.popen(os.environ['PAGER'], 'w')
            else:
                fp = ui
            generator = email.Generator.Generator(fp, mangle_from_=False)
            try:
                generator.flatten(m, 0)
                fp.write('\n')
            except IOError, inst:
                if inst.errno != errno.EPIPE:
                    raise
            if fp is not ui:
                fp.close()
        else:
            if not sendmail:
                verifycert = ui.config('smtp', 'verifycert')
                if opts.get('insecure'):
                    ui.setconfig('smtp', 'verifycert', 'loose', 'patchbomb')
                try:
                    sendmail = mail.connect(ui, mbox=mbox)
                finally:
                    ui.setconfig('smtp', 'verifycert', verifycert, 'patchbomb')
            ui.status(_('sending '), subj, ' ...\n')
            ui.progress(_('sending'), i, item=subj, total=len(msgs))
            if not mbox:
                # Exim does not remove the Bcc field
                del m['Bcc']
            fp = cStringIO.StringIO()
            generator = email.Generator.Generator(fp, mangle_from_=False)
            generator.flatten(m, 0)
            sendmail(sender_addr, to + bcc + cc, fp.getvalue())
Пример #16
0
from mercurial import revset
from mercurial import scmutil
from mercurial import templatekw
from mercurial import util as hgutil
from mercurial import url
from mercurial.i18n import _

demandimport.ignore.extend([
    'collections',
])

import gitrepo, hgrepo
from git_handler import GitHandler
import verify

version = [int(x) for x in hgutil.version().split('.')]
versionTotal = version[0] * 10000 + version[1] * 100 + version[2]

testedwith = '2.8.2 3.0.1'
buglink = 'https://bitbucket.org/durin42/hg-git/issues'

cmdtable = {}
command = cmdutil.command(cmdtable)

# support for `hg clone git://github.com/defunkt/facebox.git`
# also hg clone git+ssh://[email protected]/schacon/simplegit.git
_gitschemes = ('git', 'git+ssh', 'git+http', 'git+https')
for _scheme in _gitschemes:
    hg.schemes[_scheme] = gitrepo

# support for `hg clone localgitrepo`
Пример #17
0
from mercurial import revset
from mercurial import scmutil
from mercurial import templatekw
from mercurial import util as hgutil
from mercurial import url
from mercurial.i18n import _

demandimport.ignore.extend([
    'collections',
    ])

import gitrepo, hgrepo
from git_handler import GitHandler
import verify

version = [int(x) for x in hgutil.version().split('.')]
versionTotal = version[0]*10000 + version[1] * 100 + version[2]

testedwith = '2.8.2 3.0.1'
buglink = 'https://bitbucket.org/durin42/hg-git/issues'

cmdtable = {}
command = cmdutil.command(cmdtable)

# support for `hg clone git://github.com/defunkt/facebox.git`
# also hg clone git+ssh://[email protected]/schacon/simplegit.git
_gitschemes = ('git', 'git+ssh', 'git+http', 'git+https')
for _scheme in _gitschemes:
    hg.schemes[_scheme] = gitrepo

# support for `hg clone localgitrepo`
Пример #18
0
##############################################################################


import re
import os.path
import sys

from mercurial import util, commands
from mercurial.config import config as config_file
from mercurial.i18n import _

sys.path.append(os.path.dirname(__file__))
from deprecate import replace_deprecated, deprecated

if util.version() >= '1.9':
    from mercurial.scmutil import rcpath, userrcpath
else:
    rcpath = util.rcpath
    userrcpath = util.userrcpath

def hgcmd(func):
    """
    function decorator, but it doesn't do anything, it's just a convenient
    label.
    """
    return func

@replace_deprecated("local_rc")
def localrc(repo=None):
    """
Пример #19
0
        def clone(self, remote, heads=[], stream=False):
            supported = True

            if (exchange and hasattr(exchange, '_maybeapplyclonebundle')
                    and remote.capable('clonebundles')):
                supported = False
                self.ui.warn(_('(mercurial client has built-in support for '
                               'bundle clone features; the "bundleclone" '
                               'extension can likely safely be removed)\n'))

                if not self.ui.configbool('experimental', 'clonebundles', False):
                    self.ui.warn(_('(but the experimental.clonebundles config '
                                   'flag is not enabled: enable it before '
                                   'disabling bundleclone or cloning from '
                                   'pre-generated bundles may not work)\n'))
                    # We assume that presence of the bundleclone extension
                    # means they want clonebundles enabled. Otherwise, why do
                    # they have bundleclone enabled? So silently enable it.
                    ui.setconfig('experimental', 'clonebundles', True)
            elif not remote.capable('bundles'):
                supported = False
                self.ui.debug(_('bundle clone not supported\n'))
            elif heads:
                supported = False
                self.ui.debug(_('cannot perform bundle clone if heads requested\n'))
            elif stream:
                supported = False
                self.ui.debug(_('ignoring bundle clone because stream was '
                                'requested\n'))

            if not supported:
                return super(bundleclonerepo, self).clone(remote, heads=heads,
                        stream=stream)

            result = remote._call('bundles')

            if not result:
                self.ui.note(_('no bundles available; using normal clone\n'))
                return super(bundleclonerepo, self).clone(remote, heads=heads,
                        stream=stream)

            pyver = sys.version_info
            pyver = (pyver[0], pyver[1], pyver[2])

            hgver = util.version()
            # Discard bit after '+'.
            hgver = hgver.split('+')[0]
            try:
                hgver = tuple([int(i) for i in hgver.split('.')[0:2]])
            except ValueError:
                hgver = (0, 0)

            # Testing backdoors.
            if ui.config('bundleclone', 'fakepyver'):
                pyver = ui.configlist('bundleclone', 'fakepyver')
                pyver = tuple(int(v) for v in pyver)

            if ui.config('bundleclone', 'fakehgver'):
                hgver = ui.configlist('bundleclone', 'fakehgver')
                hgver = tuple(int(v) for v in hgver[0:2])

            entries = []
            snifilteredfrompython = False
            snifilteredfromhg = False

            for line in result.splitlines():
                fields = line.split()
                url = fields[0]
                attrs = {}
                for rawattr in fields[1:]:
                    key, value = rawattr.split('=', 1)
                    attrs[urllib.unquote(key)] = urllib.unquote(value)

                # Filter out SNI entries if we don't support SNI.
                if attrs.get('requiresni') == 'true':
                    skip = False
                    if pyver < (2, 7, 9):
                        # Take this opportunity to inform people they are using an
                        # old, insecure Python.
                        if not snifilteredfrompython:
                            self.ui.warn(_('(your Python is older than 2.7.9 '
                                           'and does not support modern and '
                                           'secure SSL/TLS; please consider '
                                           'upgrading your Python to a secure '
                                           'version)\n'))
                        snifilteredfrompython = True
                        skip = True

                    if hgver < (3, 3):
                        if not snifilteredfromhg:
                            self.ui.warn(_('(you Mercurial is old and does '
                                           'not support modern and secure '
                                           'SSL/TLS; please consider '
                                           'upgrading your Mercurial to 3.3+ '
                                           'which supports modern and secure '
                                           'SSL/TLS)\n'))
                        snifilteredfromhg = True
                        skip = True

                    if skip:
                        self.ui.warn(_('(ignoring URL on server that requires '
                                       'SNI)\n'))
                        continue

                entries.append((url, attrs))

            if not entries:
                # Don't fall back to normal clone because we don't want mass
                # fallback in the wild to barage servers expecting bundle
                # offload.
                raise util.Abort(_('no appropriate bundles available'),
                                 hint=_('you may wish to complain to the '
                                        'server operator'))

            # The configuration is allowed to define lists of preferred
            # attributes and values. If this is present, sort results according
            # to that preference. Otherwise, use manifest order and select the
            # first entry.
            prefers = self.ui.configlist('bundleclone', 'prefers', default=[])
            if prefers:
                prefers = [p.split('=', 1) for p in prefers]

                def compareentry(a, b):
                    aattrs = a[1]
                    battrs = b[1]

                    # Itereate over local preferences.
                    for pkey, pvalue in prefers:
                        avalue = aattrs.get(pkey)
                        bvalue = battrs.get(pkey)

                        # Special case for b is missing attribute and a matches
                        # exactly.
                        if avalue is not None and bvalue is None and avalue == pvalue:
                            return -1

                        # Special case for a missing attribute and b matches
                        # exactly.
                        if bvalue is not None and avalue is None and bvalue == pvalue:
                            return 1

                        # We can't compare unless the attribute is defined on
                        # both entries.
                        if avalue is None or bvalue is None:
                            continue

                        # Same values should fall back to next attribute.
                        if avalue == bvalue:
                            continue

                        # Exact matches come first.
                        if avalue == pvalue:
                            return -1
                        if bvalue == pvalue:
                            return 1

                        # Fall back to next attribute.
                        continue

                    # Entries could not be sorted based on attributes. This
                    # says they are equal, which will fall back to index order,
                    # which is what we want.
                    return 0

                entries = sorted(entries, cmp=compareentry)

            url, attrs = entries[0]

            if not url:
                self.ui.note(_('invalid bundle manifest; using normal clone\n'))
                return super(bundleclonerepo, self).clone(remote, heads=heads,
                        stream=stream)

            self.ui.status(_('downloading bundle %s\n' % url))

            try:
                fh = hgurl.open(self.ui, url)
                # Stream clone data is not changegroup data. Handle it
                # specially.
                if 'stream' in attrs:
                    reqs = set(attrs['stream'].split(','))
                    l = fh.readline()
                    filecount, bytecount = map(int, l.split(' ', 1))
                    self.ui.status(_('streaming all changes\n'))
                    consumev1(self, fh, filecount, bytecount)
                else:
                    if exchange:
                        cg = exchange.readbundle(self.ui, fh, 'stream')
                    else:
                        cg = changegroup.readbundle(fh, 'stream')

                    # Mercurial 3.6 introduced cgNunpacker.apply().
                    # Before that, there was changegroup.addchangegroup().
                    # Before that, there was localrepository.addchangegroup().
                    if hasattr(cg, 'apply'):
                        cg.apply(self, 'bundleclone', url)
                    elif hasattr(changegroup, 'addchangegroup'):
                        changegroup.addchangegroup(self, cg, 'bundleclone', url)
                    else:
                        self.addchangegroup(cg, 'bundleclone', url)

                self.ui.status(_('finishing applying bundle; pulling\n'))
                # Maintain compatibility with Mercurial 2.x.
                if exchange:
                    return exchange.pull(self, remote, heads=heads)
                else:
                    return self.pull(remote, heads=heads)

            except (urllib2.HTTPError, urllib2.URLError) as e:
                if isinstance(e, urllib2.HTTPError):
                    msg = _('HTTP error fetching bundle: %s') % str(e)
                else:
                    msg = _('error fetching bundle: %s') % e.reason

                # Don't fall back to regular clone unless explicitly told to.
                if not self.ui.configbool('bundleclone', 'fallbackonerror', False):
                    raise util.Abort(msg, hint=_('consider contacting the '
                        'server operator if this error persists'))

                self.ui.warn(msg + '\n')
                self.ui.warn(_('falling back to normal clone\n'))

                return super(bundleclonerepo, self).clone(remote, heads=heads,
                        stream=stream)
Пример #20
0
def collapse(ui, repo, **opts):
    """collapse multiple revisions into one

    Collapse combines multiple consecutive changesets into a single changeset,
    preserving any descendants of the final changeset. The commit messages for
    the collapsed changesets are concatenated and may be edited before the
    collapse is completed.
    """

    hg_vsn = re.match(r"[0-9.]+", util.version()).group(0)
    vsn_list = [int(x) for x in hg_vsn.split(".")]
    if vsn_list < [2, 0]:
        raise util.Abort(
            _('Mercurial version to low (%s), '
              'you need at least 2.0') % hg_vsn)

    try:
        from mercurial import scmutil
        rng = scmutil.revrange(repo, opts['rev'])
    except ImportError:
        rng = cmdutil.revrange(repo, opts['rev'])

    # run collapse in the repository root
    olddir = os.getcwd()
    os.chdir(repo.root)
    try:
        if opts['movelog']:
            movelog = open(opts['movelog'], 'a')
        else:
            movelog = False

        if opts['timedelta']:
            timedelta = float(opts['timedelta'])
        else:
            timedelta = float('inf')

        if not opts['auto']:
            if not rng:
                raise util.Abort(_('no revisions specified'))

            if opts['timedelta']:
                raise util.Abort(_('-t or --timedelta only valid with --auto'))
            if opts['userchange']:
                raise util.Abort(
                    _('-u or --userchange only valid with --auto'))
            # FIXME add more options that don't work
            # FIXME FIXME: rework ui to make the distinction between auto
            # and not unnecessary.  Integrate revsets (event disjoint)

            first = rng[0]
            last = rng[-1]
            revs = inbetween(repo, first, last)

            if not revs:
                raise util.Abort(
                    _('revision %s is not an ancestor '
                      'of revision %s\n') % (first, last))
            elif len(revs) == 1:
                raise util.Abort(_('only one revision specified'))
            do_collapse(ui, repo, first, last, revs, movelog, timedelta, opts)

        else:  # auto mode
            if len(rng) == 0:
                start = 0
            elif len(rng) == 1:
                start = rng[0]
            else:
                util.Abort(_('multiple revisions specified with auto mode'))

            count = 0
            while count < 1 or opts['repeat']:
                if opts['usefirst']:
                    revs = find_first_chunk(ui, repo, start, timedelta, opts)
                else:
                    revs = find_last_chunk(ui, repo, start, timedelta, opts)

                if not revs:
                    if count == 0:
                        raise util.Abort(_('no revision chunk found\n'))
                    else:
                        break

                first = min(revs)
                last = max(revs)

                assert len(revs) > 1
                do_collapse(ui, repo, first, last, revs, movelog, timedelta,
                            opts)
                count += 1
    finally:
        os.chdir(olddir)
Пример #21
0
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# http://bitbucket.org/sdorra/scm-manager
#
#

import sys
from mercurial import util
from xml.dom.minidom import Document

pyVersion = sys.version_info
pyVersion = str(pyVersion.major) + "." + str(pyVersion.minor) + "." + str(
    pyVersion.micro)
hgVersion = util.version()

doc = Document()
root = doc.createElement('version')

pyNode = doc.createElement('python')
pyNode.appendChild(doc.createTextNode(pyVersion))
root.appendChild(pyNode)

hgNode = doc.createElement('mercurial')
hgNode.appendChild(doc.createTextNode(hgVersion))
root.appendChild(hgNode)

doc.appendChild(root)
doc.writexml(sys.stdout, encoding='UTF-8')
Пример #22
0
def robustcheckout(ui, url, dest, upstream=None, revision=None, branch=None,
                   purge=False, sharebase=None, networkattempts=None,
                   sparseprofile=None):
    """Ensure a working copy has the specified revision checked out.

    Repository data is automatically pooled into the common directory
    specified by ``--sharebase``, which is a required argument. It is required
    because pooling storage prevents excessive cloning, which makes operations
    complete faster.

    One of ``--revision`` or ``--branch`` must be specified. ``--revision``
    is preferred, as it is deterministic and there is no ambiguity as to which
    revision will actually be checked out.

    If ``--upstream`` is used, the repo at that URL is used to perform the
    initial clone instead of cloning from the repo where the desired revision
    is located.

    ``--purge`` controls whether to removed untracked and ignored files from
    the working directory. If used, the end state of the working directory
    should only contain files explicitly under version control for the requested
    revision.

    ``--sparseprofile`` can be used to specify a sparse checkout profile to use.
    The sparse checkout profile corresponds to a file in the revision to be
    checked out. If a previous sparse profile or config is present, it will be
    replaced by this sparse profile. We choose not to "widen" the sparse config
    so operations are as deterministic as possible. If an existing checkout
    is present and it isn't using a sparse checkout, we error. This is to
    prevent accidentally enabling sparse on a repository that may have
    clients that aren't sparse aware. Sparse checkout support requires Mercurial
    4.3 or newer and the ``sparse`` extension must be enabled.
    """
    if not revision and not branch:
        raise error.Abort('must specify one of --revision or --branch')

    if revision and branch:
        raise error.Abort('cannot specify both --revision and --branch')

    # Require revision to look like a SHA-1.
    if revision:
        if len(revision) < 12 or len(revision) > 40 or not re.match('^[a-f0-9]+$', revision):
            raise error.Abort('--revision must be a SHA-1 fragment 12-40 '
                              'characters long')

    sharebase = sharebase or ui.config('share', 'pool')
    if not sharebase:
        raise error.Abort('share base directory not defined; refusing to operate',
                          hint='define share.pool config option or pass --sharebase')

    # Sparse profile support was added in Mercurial 4.3, where it was highly
    # experimental. Because of the fragility of it, we only support sparse
    # profiles on 4.3. When 4.4 is released, we'll need to opt in to sparse
    # support. We /could/ silently fall back to non-sparse when not supported.
    # However, given that sparse has performance implications, we want to fail
    # fast if we can't satisfy the desired checkout request.
    if sparseprofile:
        if util.versiontuple(n=2) not in ((4, 3), (4, 4), (4, 5)):
            raise error.Abort('sparse profile support only available for '
                              'Mercurial versions greater than 4.3 (using %s)' % util.version())

        try:
            extensions.find('sparse')
        except KeyError:
            raise error.Abort('sparse extension must be enabled to use '
                              '--sparseprofile')

    ui.warn('(using Mercurial %s)\n' % util.version())

    # worker.backgroundclose only makes things faster if running anti-virus,
    # which our automation doesn't. Disable it.
    ui.setconfig('worker', 'backgroundclose', False)

    # By default the progress bar starts after 3s and updates every 0.1s. We
    # change this so it shows and updates every 1.0s.
    # We also tell progress to assume a TTY is present so updates are printed
    # even if there is no known TTY.
    # We make the config change here instead of in a config file because
    # otherwise we're at the whim of whatever configs are used in automation.
    ui.setconfig('progress', 'delay', 1.0)
    ui.setconfig('progress', 'refresh', 1.0)
    ui.setconfig('progress', 'assume-tty', True)

    sharebase = os.path.realpath(sharebase)

    return _docheckout(ui, url, dest, upstream, revision, branch, purge,
                       sharebase, networkattempts,
                       sparse_profile=sparseprofile)
Пример #23
0
def patchbomb(ui, repo, *revs, **opts):
    '''send changesets by email

    By default, diffs are sent in the format generated by hg export,
    one per message. The series starts with a "[PATCH 0 of N]"
    introduction, which describes the series as a whole.

    Each patch email has a Subject line of "[PATCH M of N] ...", using
    the first line of the changeset description as the subject text.
    The message contains two or three parts. First, the changeset
    description. Next, (optionally) if the diffstat program is
    installed and -d/--diffstat is used, the result of running
    diffstat on the patch. Finally, the patch itself, as generated by
    "hg export".

    By default the patch is included as text in the email body for
    easy reviewing. Using the -a/--attach option will instead create
    an attachment for the patch. With -i/--inline an inline attachment
    will be created.

    With -o/--outgoing, emails will be generated for patches not found
    in the destination repository (or only those which are ancestors
    of the specified revisions if any are provided)

    With -b/--bundle, changesets are selected as for --outgoing, but a
    single email containing a binary Mercurial bundle as an attachment
    will be sent.

    Examples::

      hg email -r 3000          # send patch 3000 only
      hg email -r 3000 -r 3001  # send patches 3000 and 3001
      hg email -r 3000:3005     # send patches 3000 through 3005
      hg email 3000             # send patch 3000 (deprecated)

      hg email -o               # send all patches not in default
      hg email -o DEST          # send all patches not in DEST
      hg email -o -r 3000       # send all ancestors of 3000 not in default
      hg email -o -r 3000 DEST  # send all ancestors of 3000 not in DEST

      hg email -b               # send bundle of all patches not in default
      hg email -b DEST          # send bundle of all patches not in DEST
      hg email -b -r 3000       # bundle of all ancestors of 3000 not in default
      hg email -b -r 3000 DEST  # bundle of all ancestors of 3000 not in DEST

    Before using this command, you will need to enable email in your
    hgrc. See the [email] section in hgrc(5) for details.
    '''

    _charsets = mail._charsets(ui)

    def outgoing(dest, revs):
        '''Return the revisions present locally but not in dest'''
        dest = ui.expandpath(dest or 'default-push', dest or 'default')
        dest, branches = hg.parseurl(dest)
        revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
        if revs:
            revs = [repo.lookup(rev) for rev in revs]
        other = hg.repository(cmdutil.remoteui(repo, opts), dest)
        ui.status(_('comparing with %s\n') % dest)
        o = repo.findoutgoing(other)
        if not o:
            ui.status(_("no changes found\n"))
            return []
        o = repo.changelog.nodesbetween(o, revs)[0]
        return [str(repo.changelog.rev(r)) for r in o]

    def getpatches(revs):
        for r in cmdutil.revrange(repo, revs):
            output = cStringIO.StringIO()
            patch.export(repo, [r], fp=output, opts=patch.diffopts(ui, opts))
            yield output.getvalue().split('\n')

    def getbundle(dest):
        tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
        tmpfn = os.path.join(tmpdir, 'bundle')
        try:
            commands.bundle(ui, repo, tmpfn, dest, **opts)
            return open(tmpfn, 'rb').read()
        finally:
            try:
                os.unlink(tmpfn)
            except:
                pass
            os.rmdir(tmpdir)

    if not (opts.get('test') or opts.get('mbox')):
        # really sending
        mail.validateconfig(ui)

    if not (revs or opts.get('rev') or opts.get('outgoing')
            or opts.get('bundle') or opts.get('patches')):
        raise util.Abort(_('specify at least one changeset with -r or -o'))

    if opts.get('outgoing') and opts.get('bundle'):
        raise util.Abort(
            _("--outgoing mode always on with --bundle;"
              " do not re-specify --outgoing"))

    if opts.get('outgoing') or opts.get('bundle'):
        if len(revs) > 1:
            raise util.Abort(_("too many destinations"))
        dest = revs and revs[0] or None
        revs = []

    if opts.get('rev'):
        if revs:
            raise util.Abort(_('use only one form to specify the revision'))
        revs = opts.get('rev')

    if opts.get('outgoing'):
        revs = outgoing(dest, opts.get('rev'))
    if opts.get('bundle'):
        opts['revs'] = revs

    # start
    if opts.get('date'):
        start_time = util.parsedate(opts.get('date'))
    else:
        start_time = util.makedate()

    def genmsgid(id):
        return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())

    def getdescription(body, sender):
        if opts.get('desc'):
            body = open(opts.get('desc')).read()
        else:
            ui.write(
                _('\nWrite the introductory message for the '
                  'patch series.\n\n'))
            body = ui.edit(body, sender)
        return body

    def getpatchmsgs(patches, patchnames=None):
        jumbo = []
        msgs = []

        ui.write(
            _('This patch series consists of %d patches.\n\n') % len(patches))

        name = None
        for i, p in enumerate(patches):
            jumbo.extend(p)
            if patchnames:
                name = patchnames[i]
            msg = makepatch(ui, repo, p, opts, _charsets, i + 1, len(patches),
                            name)
            msgs.append(msg)

        if len(patches) > 1 or opts.get('intro'):
            tlen = len(str(len(patches)))

            flag = ' '.join(opts.get('flag'))
            if flag:
                subj = '[PATCH %0*d of %d %s]' % (tlen, 0, len(patches), flag)
            else:
                subj = '[PATCH %0*d of %d]' % (tlen, 0, len(patches))
            subj += ' ' + (opts.get('subject')
                           or prompt(ui, 'Subject: ', rest=subj))

            body = ''
            if opts.get('diffstat'):
                d = cdiffstat(ui, _('Final summary:\n'), jumbo)
                if d:
                    body = '\n' + d

            body = getdescription(body, sender)
            msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
            msg['Subject'] = mail.headencode(ui, subj, _charsets,
                                             opts.get('test'))

            msgs.insert(0, (msg, subj))
        return msgs

    def getbundlemsgs(bundle):
        subj = (opts.get('subject')
                or prompt(ui, 'Subject:', 'A bundle for your repository'))

        body = getdescription('', sender)
        msg = email.MIMEMultipart.MIMEMultipart()
        if body:
            msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
        datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
        datapart.set_payload(bundle)
        bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
        datapart.add_header('Content-Disposition',
                            'attachment',
                            filename=bundlename)
        email.Encoders.encode_base64(datapart)
        msg.attach(datapart)
        msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
        return [(msg, subj)]

    sender = (opts.get('from') or ui.config('email', 'from')
              or ui.config('patchbomb', 'from')
              or prompt(ui, 'From', ui.username()))

    # internal option used by pbranches
    patches = opts.get('patches')
    if patches:
        msgs = getpatchmsgs(patches, opts.get('patchnames'))
    elif opts.get('bundle'):
        msgs = getbundlemsgs(getbundle(dest))
    else:
        msgs = getpatchmsgs(list(getpatches(revs)))

    def getaddrs(opt, prpt=None, default=None):
        if opts.get(opt):
            return mail.addrlistencode(ui, opts.get(opt), _charsets,
                                       opts.get('test'))

        addrs = (ui.config('email', opt) or ui.config('patchbomb', opt) or '')
        if not addrs and prpt:
            addrs = prompt(ui, prpt, default)

        return mail.addrlistencode(ui, [addrs], _charsets, opts.get('test'))

    to = getaddrs('to', 'To')
    cc = getaddrs('cc', 'Cc', '')
    bcc = getaddrs('bcc')

    ui.write('\n')

    parent = opts.get('in_reply_to') or None
    # angle brackets may be omitted, they're not semantically part of the msg-id
    if parent is not None:
        if not parent.startswith('<'):
            parent = '<' + parent
        if not parent.endswith('>'):
            parent += '>'

    first = True

    sender_addr = email.Utils.parseaddr(sender)[1]
    sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
    sendmail = None
    for m, subj in msgs:
        try:
            m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
        except TypeError:
            m['Message-Id'] = genmsgid('patchbomb')
        if parent:
            m['In-Reply-To'] = parent
            m['References'] = parent
        if first:
            parent = m['Message-Id']
            first = False

        m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
        m['Date'] = email.Utils.formatdate(start_time[0], localtime=True)

        start_time = (start_time[0] + 1, start_time[1])
        m['From'] = sender
        m['To'] = ', '.join(to)
        if cc:
            m['Cc'] = ', '.join(cc)
        if bcc:
            m['Bcc'] = ', '.join(bcc)
        if opts.get('test'):
            ui.status(_('Displaying '), subj, ' ...\n')
            ui.flush()
            if 'PAGER' in os.environ:
                fp = util.popen(os.environ['PAGER'], 'w')
            else:
                fp = ui
            generator = email.Generator.Generator(fp, mangle_from_=False)
            try:
                generator.flatten(m, 0)
                fp.write('\n')
            except IOError, inst:
                if inst.errno != errno.EPIPE:
                    raise
            if fp is not ui:
                fp.close()
        elif opts.get('mbox'):
            ui.status(_('Writing '), subj, ' ...\n')
            fp = open(opts.get('mbox'), 'In-Reply-To' in m and 'ab+' or 'wb+')
            generator = email.Generator.Generator(fp, mangle_from_=True)
            # Should be time.asctime(), but Windows prints 2-characters day
            # of month instead of one. Make them print the same thing.
            date = time.strftime('%a %b %d %H:%M:%S %Y',
                                 time.localtime(start_time[0]))
            fp.write('From %s %s\n' % (sender_addr, date))
            generator.flatten(m, 0)
            fp.write('\n\n')
            fp.close()
Пример #24
0
Файл: hg.py Проект: gitmob/yabbe
 def _vcs_version(self):
     if version == None:
         return None
     return version()
Пример #25
0
def main(args):
    global prefix, gitdir, dirname, branches, bmarks
    global marks, blob_marks, parsed_refs
    global peer, mode, bad_mail, bad_name
    global track_branches, force_push, is_tmp
    global parsed_tags
    global filenodes
    global fake_bmark, hg_version
    global dry_run
    global notes, alias

    marks = None
    is_tmp = False
    gitdir = os.environ.get('GIT_DIR', None)

    if len(args) < 3:
        die('Not enough arguments.')

    if not gitdir:
        die('GIT_DIR not set')

    alias = args[1]
    url = args[2]
    peer = None

    hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
    track_branches = get_config_bool('remote-hg.track-branches', True)
    force_push = False

    if hg_git_compat:
        mode = 'hg'
        bad_mail = 'none@none'
        bad_name = ''
    else:
        mode = 'git'
        bad_mail = 'unknown'
        bad_name = 'Unknown'

    if alias[4:] == url:
        is_tmp = True
        alias = hashlib.sha1(alias).hexdigest()

    dirname = os.path.join(gitdir, 'hg', alias)
    branches = {}
    bmarks = {}
    blob_marks = {}
    parsed_refs = {}
    parsed_tags = {}
    filenodes = {}
    fake_bmark = None
    try:
        version, _, extra = util.version().partition('+')
        version = list(int(e) for e in version.split('.'))
        if extra:
            version[1] += 1
        hg_version = tuple(version)
    except:
        hg_version = None
    dry_run = False
    notes = set()

    repo = get_repo(url, alias)
    prefix = 'refs/hg/%s' % alias

    if not is_tmp:
        fix_path(alias, peer or repo, url)

    marks_path = os.path.join(dirname, 'marks-hg')
    marks = Marks(marks_path, repo)

    if sys.platform == 'win32':
        import msvcrt
        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)

    parser = Parser(repo)
    for line in parser:
        if parser.check('capabilities'):
            do_capabilities(parser)
        elif parser.check('list'):
            do_list(parser)
        elif parser.check('import'):
            do_import(parser)
        elif parser.check('export'):
            do_export(parser)
        elif parser.check('option'):
            do_option(parser)
        else:
            die('unhandled command: %s' % line)
        sys.stdout.flush()

    marks.store()
Пример #26
0
def collapse(ui, repo, **opts):
    """collapse multiple revisions into one

    Collapse combines multiple consecutive changesets into a single changeset,
    preserving any descendants of the final changeset. The commit messages for
    the collapsed changesets are concatenated and may be edited before the
    collapse is completed.
    """

    hg_vsn = util.version()
    vsn_list = [ int(x) for x in hg_vsn.split(".") ]
    if vsn_list < [2,0]:
        raise util.Abort(_('Mercurial version to low (%s), '
                           'you need at least 2.0') % hg_vsn)

    try:
        from mercurial import scmutil
        rng = scmutil.revrange(repo, opts['rev'])
    except ImportError:
        rng = cmdutil.revrange(repo, opts['rev'])

    if opts['movelog']:
        movelog = open(opts['movelog'], 'a')
    else:
        movelog = False
            
    if opts['timedelta']:
        timedelta = float(opts['timedelta'])
    else:
        timedelta = float('inf')

    if not opts['auto']:
        if not rng:
            raise util.Abort(_('no revisions specified'))

        if opts['timedelta']:
            raise util.Abort(_('-t or --timedelta only valid with --auto'))
        if opts['userchange']:
            raise util.Abort(_('-u or --userchange only valid with --auto'))
        # FIXME add more options that don't work
        # FIXME FIXME: rework ui to make the distinction between auto
        # and not unnecessary.  Integrate revsets (event disjoint)

        first = rng[0]
        last = rng[-1]
        revs = inbetween(repo, first, last)

        if not revs:
            raise util.Abort(_('revision %s is not an ancestor '
                               'of revision %s\n') % (first, last))
        elif len(revs) == 1:
            raise util.Abort(_('only one revision specified'))
        do_collapse(ui, repo, first, last, revs, movelog, timedelta,
            opts)

    else:                       # auto mode
        if len(rng) == 0:
            start = 0
        elif len(rng) == 1:
            start = rng[0]
        else:
            util.Abort(_('multiple revisions specified with auto mode'))

        count = 0
        while count < 1 or opts['repeat']:
            if opts['usefirst']:
                revs = find_first_chunk(ui, repo, start, timedelta, opts)
            else:
                revs = find_last_chunk(ui, repo, start, timedelta, opts)

            if not revs:
                if count == 0:
                    raise util.Abort(_('no revision chunk found\n'))
                else:
                    break

            first = min(revs)
            last = max(revs)

            assert len(revs) > 1
            do_collapse(ui, repo, first, last, revs, movelog, timedelta, opts)
            count += 1
def robustcheckout(
    ui,
    url,
    dest,
    upstream=None,
    revision=None,
    branch=None,
    purge=False,
    sharebase=None,
    networkattempts=None,
    sparseprofile=None,
):
    """Ensure a working copy has the specified revision checked out.

    Repository data is automatically pooled into the common directory
    specified by ``--sharebase``, which is a required argument. It is required
    because pooling storage prevents excessive cloning, which makes operations
    complete faster.

    One of ``--revision`` or ``--branch`` must be specified. ``--revision``
    is preferred, as it is deterministic and there is no ambiguity as to which
    revision will actually be checked out.

    If ``--upstream`` is used, the repo at that URL is used to perform the
    initial clone instead of cloning from the repo where the desired revision
    is located.

    ``--purge`` controls whether to removed untracked and ignored files from
    the working directory. If used, the end state of the working directory
    should only contain files explicitly under version control for the requested
    revision.

    ``--sparseprofile`` can be used to specify a sparse checkout profile to use.
    The sparse checkout profile corresponds to a file in the revision to be
    checked out. If a previous sparse profile or config is present, it will be
    replaced by this sparse profile. We choose not to "widen" the sparse config
    so operations are as deterministic as possible. If an existing checkout
    is present and it isn't using a sparse checkout, we error. This is to
    prevent accidentally enabling sparse on a repository that may have
    clients that aren't sparse aware. Sparse checkout support requires Mercurial
    4.3 or newer and the ``sparse`` extension must be enabled.
    """
    if not revision and not branch:
        raise error.Abort("must specify one of --revision or --branch")

    if revision and branch:
        raise error.Abort("cannot specify both --revision and --branch")

    # Require revision to look like a SHA-1.
    if revision:
        if (
            len(revision) < 12
            or len(revision) > 40
            or not re.match("^[a-f0-9]+$", revision)
        ):
            raise error.Abort(
                "--revision must be a SHA-1 fragment 12-40 " "characters long"
            )

    sharebase = sharebase or ui.config("share", "pool")
    if not sharebase:
        raise error.Abort(
            "share base directory not defined; refusing to operate",
            hint="define share.pool config option or pass --sharebase",
        )

    # Sparse profile support was added in Mercurial 4.3, where it was highly
    # experimental. Because of the fragility of it, we only support sparse
    # profiles on 4.3. When 4.4 is released, we'll need to opt in to sparse
    # support. We /could/ silently fall back to non-sparse when not supported.
    # However, given that sparse has performance implications, we want to fail
    # fast if we can't satisfy the desired checkout request.
    if sparseprofile:
        if not supported_hg():
            raise error.Abort(
                "sparse profile support only available for "
                "Mercurial versions greater than 4.3 (using %s)" % util.version()
            )

        try:
            extensions.find("sparse")
        except KeyError:
            raise error.Abort(
                "sparse extension must be enabled to use " "--sparseprofile"
            )

    ui.warn("(using Mercurial %s)\n" % util.version())

    # worker.backgroundclose only makes things faster if running anti-virus,
    # which our automation doesn't. Disable it.
    ui.setconfig("worker", "backgroundclose", False)

    # By default the progress bar starts after 3s and updates every 0.1s. We
    # change this so it shows and updates every 1.0s.
    # We also tell progress to assume a TTY is present so updates are printed
    # even if there is no known TTY.
    # We make the config change here instead of in a config file because
    # otherwise we're at the whim of whatever configs are used in automation.
    ui.setconfig("progress", "delay", 1.0)
    ui.setconfig("progress", "refresh", 1.0)
    ui.setconfig("progress", "assume-tty", True)

    sharebase = os.path.realpath(sharebase)

    optimes = []
    start = time.time()

    try:
        return _docheckout(
            ui,
            url,
            dest,
            upstream,
            revision,
            branch,
            purge,
            sharebase,
            optimes,
            networkattempts,
            sparse_profile=sparseprofile,
        )
    finally:
        overall = time.time() - start
        optimes.append(("overall", overall))

        if "TASKCLUSTER_INSTANCE_TYPE" in os.environ:
            perfherder = {"framework": {"name": "vcs"}, "suites": []}
            for op, duration in optimes:
                perfherder["suites"].append(
                    {
                        "name": op,
                        "value": duration,
                        "lowerIsBetter": True,
                        "shouldAlert": False,
                        "extraOptions": [os.environ["TASKCLUSTER_INSTANCE_TYPE"]],
                        "subtests": [],
                    }
                )

            ui.write("PERFHERDER_DATA: %s\n" % json.dumps(perfherder, sort_keys=True))
Пример #28
0
def robustcheckout(
    ui,
    url,
    dest,
    upstream=None,
    revision=None,
    branch=None,
    purge=False,
    sharebase=None,
    networkattempts=None,
    sparseprofile=None,
    noupdate=False,
):
    """Ensure a working copy has the specified revision checked out.

    Repository data is automatically pooled into the common directory
    specified by ``--sharebase``, which is a required argument. It is required
    because pooling storage prevents excessive cloning, which makes operations
    complete faster.

    One of ``--revision`` or ``--branch`` must be specified. ``--revision``
    is preferred, as it is deterministic and there is no ambiguity as to which
    revision will actually be checked out.

    If ``--upstream`` is used, the repo at that URL is used to perform the
    initial clone instead of cloning from the repo where the desired revision
    is located.

    ``--purge`` controls whether to removed untracked and ignored files from
    the working directory. If used, the end state of the working directory
    should only contain files explicitly under version control for the requested
    revision.

    ``--sparseprofile`` can be used to specify a sparse checkout profile to use.
    The sparse checkout profile corresponds to a file in the revision to be
    checked out. If a previous sparse profile or config is present, it will be
    replaced by this sparse profile. We choose not to "widen" the sparse config
    so operations are as deterministic as possible. If an existing checkout
    is present and it isn't using a sparse checkout, we error. This is to
    prevent accidentally enabling sparse on a repository that may have
    clients that aren't sparse aware. Sparse checkout support requires Mercurial
    4.3 or newer and the ``sparse`` extension must be enabled.
    """
    if not revision and not branch:
        raise error.Abort(b"must specify one of --revision or --branch")

    if revision and branch:
        raise error.Abort(b"cannot specify both --revision and --branch")

    # Require revision to look like a SHA-1.
    if revision:
        if (
            len(revision) < 12
            or len(revision) > 40
            or not re.match(b"^[a-f0-9]+$", revision)
        ):
            raise error.Abort(
                b"--revision must be a SHA-1 fragment 12-40 " b"characters long"
            )

    sharebase = sharebase or ui.config(b"share", b"pool")
    if not sharebase:
        raise error.Abort(
            b"share base directory not defined; refusing to operate",
            hint=b"define share.pool config option or pass --sharebase",
        )

    # Sparse profile support was added in Mercurial 4.3, where it was highly
    # experimental. Because of the fragility of it, we only support sparse
    # profiles on 4.3. When 4.4 is released, we'll need to opt in to sparse
    # support. We /could/ silently fall back to non-sparse when not supported.
    # However, given that sparse has performance implications, we want to fail
    # fast if we can't satisfy the desired checkout request.
    if sparseprofile:
        try:
            extensions.find(b"sparse")
        except KeyError:
            raise error.Abort(
                b"sparse extension must be enabled to use " b"--sparseprofile"
            )

    ui.warn(b"(using Mercurial %s)\n" % util.version())

    # worker.backgroundclose only makes things faster if running anti-virus,
    # which our automation doesn't. Disable it.
    ui.setconfig(b"worker", b"backgroundclose", False)

    # By default the progress bar starts after 3s and updates every 0.1s. We
    # change this so it shows and updates every 1.0s.
    # We also tell progress to assume a TTY is present so updates are printed
    # even if there is no known TTY.
    # We make the config change here instead of in a config file because
    # otherwise we're at the whim of whatever configs are used in automation.
    ui.setconfig(b"progress", b"delay", 1.0)
    ui.setconfig(b"progress", b"refresh", 1.0)
    ui.setconfig(b"progress", b"assume-tty", True)

    sharebase = os.path.realpath(sharebase)

    optimes = []
    behaviors = set()
    start = time.time()

    try:
        return _docheckout(
            ui,
            url,
            dest,
            upstream,
            revision,
            branch,
            purge,
            sharebase,
            optimes,
            behaviors,
            networkattempts,
            sparse_profile=sparseprofile,
            noupdate=noupdate,
        )
    finally:
        overall = time.time() - start

        # We store the overall time multiple ways in order to help differentiate
        # the various "flavors" of operations.

        # ``overall`` is always the total operation time.
        optimes.append(("overall", overall))

        def record_op(name):
            # If special behaviors due to "corrupt" storage occur, we vary the
            # name to convey that.
            if "remove-store" in behaviors:
                name += "_rmstore"
            if "remove-wdir" in behaviors:
                name += "_rmwdir"

            optimes.append((name, overall))

        # We break out overall operations primarily by their network interaction
        # We have variants within for working directory operations.
        if "clone" in behaviors and "create-store" in behaviors:
            record_op("overall_clone")

            if "sparse-update" in behaviors:
                record_op("overall_clone_sparsecheckout")
            else:
                record_op("overall_clone_fullcheckout")

        elif "pull" in behaviors or "clone" in behaviors:
            record_op("overall_pull")

            if "sparse-update" in behaviors:
                record_op("overall_pull_sparsecheckout")
            else:
                record_op("overall_pull_fullcheckout")

            if "empty-wdir" in behaviors:
                record_op("overall_pull_emptywdir")
            else:
                record_op("overall_pull_populatedwdir")

        else:
            record_op("overall_nopull")

            if "sparse-update" in behaviors:
                record_op("overall_nopull_sparsecheckout")
            else:
                record_op("overall_nopull_fullcheckout")

            if "empty-wdir" in behaviors:
                record_op("overall_nopull_emptywdir")
            else:
                record_op("overall_nopull_populatedwdir")

        server_url = urllibcompat.urlreq.urlparse(url).netloc

        if "TASKCLUSTER_INSTANCE_TYPE" in os.environ:
            perfherder = {
                "framework": {
                    "name": "vcs",
                },
                "suites": [],
            }
            for op, duration in optimes:
                perfherder["suites"].append(
                    {
                        "name": op,
                        "value": duration,
                        "lowerIsBetter": True,
                        "shouldAlert": False,
                        "serverUrl": server_url.decode("utf-8"),
                        "hgVersion": util.version().decode("utf-8"),
                        "extraOptions": [os.environ["TASKCLUSTER_INSTANCE_TYPE"]],
                        "subtests": [],
                    }
                )
            ui.write(
                b"PERFHERDER_DATA: %s\n"
                % pycompat.bytestr(json.dumps(perfherder, sort_keys=True))
            )
Пример #29
0
def scmversion(ui, **opts):
  pythonVersion = platform.python_version().encode("utf-8")
  ui.write(b"python/%s mercurial/%s\n" % (pythonVersion, util.version()))
Пример #30
0
def getconfigs(ui, repo):
    """
    Get a sequence of possible configuration files, including local
    (repository), user, and global.

    Each item in the returned sequence is a dictionary with the following keys:

    `scope`
        One of 'local', 'user', or 'global'.

    `path`
        The filesystem path to the config file.

    `exists`
        A `bool` indicating whether or not the file currently exists on the
        filesystem.

    `writeable`
        A `bool` indicating whether or not the file is writeable by the
        current user.

    """
    allconfigs = rcpath()
    # From 4.2 rcpath(rcutil.rccomponents) returns a tuple
    # Not checking here on isinstance, If return type changes, this will probably break instead of silently ignoring
    # this and treating the output as a string like before 4.2.
    if util.version() >= b'4.2':
        allconfigs = [c[1] for c in allconfigs if c[0] == b'path']
    local_config = localrc(repo)
    if local_config is not None:
        # rcpath() returns a reference to a global list, must not modify
        # it in place by "+=" but instead create a copy by "+".
        allconfigs = allconfigs + [local_config]
    userconfigs = set(userrcpath())

    configs = []
    paths = set()

    # for all global configs
    for f in allconfigs:
        if f in paths:
            continue
        paths.add(f)

        if f == local_config:
            scope = b'local'
        elif f in userconfigs:
            scope = b'user'
        else:
            scope = b'global'
        if not os.path.exists(f):
            exists = False
            writeable = False
        else:
            exists = True
            if os.access(f, os.W_OK):
                writeable = True
            else:
                writeable = False
        configs.append({b'scope': scope, b'path': f, b'exists': exists,
                        b'writeable': writeable})

    return configs
Пример #31
0
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# http://bitbucket.org/sdorra/scm-manager
#
#

import sys
from mercurial import util
from xml.dom.minidom import Document

pyVersion = sys.version_info
pyVersion = str(pyVersion.major) + "." + str(pyVersion.minor) + "." + str(pyVersion.micro)
hgVersion = util.version()

doc = Document()
root = doc.createElement('version')

pyNode = doc.createElement('python')
pyNode.appendChild(doc.createTextNode(pyVersion))
root.appendChild(pyNode)

hgNode = doc.createElement('mercurial')
hgNode.appendChild(doc.createTextNode(hgVersion))
root.appendChild(hgNode)

doc.appendChild(root)
doc.writexml(sys.stdout, encoding='UTF-8')
Пример #32
0
def main(args):
    global prefix, gitdir, dirname, branches, bmarks
    global marks, blob_marks, parsed_refs
    global peer, mode, bad_mail, bad_name
    global track_branches, force_push, is_tmp
    global parsed_tags
    global filenodes
    global fake_bmark, hg_version
    global dry_run
    global notes, alias

    marks = None
    is_tmp = False
    gitdir = os.environ.get('GIT_DIR', None)

    if len(args) < 3:
        die('Not enough arguments.')

    if not gitdir:
        die('GIT_DIR not set')

    alias = args[1]
    url = args[2]
    peer = None

    hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
    track_branches = get_config_bool('remote-hg.track-branches', True)
    force_push = False

    if hg_git_compat:
        mode = 'hg'
        bad_mail = 'none@none'
        bad_name = ''
    else:
        mode = 'git'
        bad_mail = 'unknown'
        bad_name = 'Unknown'

    if alias[4:] == url:
        is_tmp = True
        alias = hashlib.sha1(alias).hexdigest()

    dirname = os.path.join(gitdir, 'hg', alias)
    branches = {}
    bmarks = {}
    blob_marks = {}
    parsed_refs = {}
    parsed_tags = {}
    filenodes = {}
    fake_bmark = None
    try:
        hg_version = tuple(int(e) for e in util.version().split('.'))
    except:
        hg_version = None
    dry_run = False
    notes = set()

    repo = get_repo(url, alias)
    prefix = 'refs/hg/%s' % alias

    if not is_tmp:
        fix_path(alias, peer or repo, url)

    marks_path = os.path.join(dirname, 'marks-hg')
    marks = Marks(marks_path, repo)

    if sys.platform == 'win32':
        import msvcrt
        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)

    parser = Parser(repo)
    for line in parser:
        if parser.check('capabilities'):
            do_capabilities(parser)
        elif parser.check('list'):
            do_list(parser)
        elif parser.check('import'):
            do_import(parser)
        elif parser.check('export'):
            do_export(parser)
        elif parser.check('option'):
            do_option(parser)
        else:
            die('unhandled command: %s' % line)
        sys.stdout.flush()
Пример #33
0
def patchbomb(ui, repo, *revs, **opts):
    '''send changesets by email

    By default, diffs are sent in the format generated by hg export,
    one per message. The series starts with a "[PATCH 0 of N]"
    introduction, which describes the series as a whole.

    Each patch email has a Subject line of "[PATCH M of N] ...", using
    the first line of the changeset description as the subject text.
    The message contains two or three parts. First, the changeset
    description. Next, (optionally) if the diffstat program is
    installed and -d/--diffstat is used, the result of running
    diffstat on the patch. Finally, the patch itself, as generated by
    "hg export".

    By default the patch is included as text in the email body for
    easy reviewing. Using the -a/--attach option will instead create
    an attachment for the patch. With -i/--inline an inline attachment
    will be created.

    With -o/--outgoing, emails will be generated for patches not found
    in the destination repository (or only those which are ancestors
    of the specified revisions if any are provided)

    With -b/--bundle, changesets are selected as for --outgoing, but a
    single email containing a binary Mercurial bundle as an attachment
    will be sent.

    Examples::

      hg email -r 3000          # send patch 3000 only
      hg email -r 3000 -r 3001  # send patches 3000 and 3001
      hg email -r 3000:3005     # send patches 3000 through 3005
      hg email 3000             # send patch 3000 (deprecated)

      hg email -o               # send all patches not in default
      hg email -o DEST          # send all patches not in DEST
      hg email -o -r 3000       # send all ancestors of 3000 not in default
      hg email -o -r 3000 DEST  # send all ancestors of 3000 not in DEST

      hg email -b               # send bundle of all patches not in default
      hg email -b DEST          # send bundle of all patches not in DEST
      hg email -b -r 3000       # bundle of all ancestors of 3000 not in default
      hg email -b -r 3000 DEST  # bundle of all ancestors of 3000 not in DEST

    Before using this command, you will need to enable email in your
    hgrc. See the [email] section in hgrc(5) for details.
    '''

    _charsets = mail._charsets(ui)

    def outgoing(dest, revs):
        '''Return the revisions present locally but not in dest'''
        dest = ui.expandpath(dest or 'default-push', dest or 'default')
        dest, branches = hg.parseurl(dest)
        revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
        if revs:
            revs = [repo.lookup(rev) for rev in revs]
        other = hg.repository(cmdutil.remoteui(repo, opts), dest)
        ui.status(_('comparing with %s\n') % dest)
        o = repo.findoutgoing(other)
        if not o:
            ui.status(_("no changes found\n"))
            return []
        o = repo.changelog.nodesbetween(o, revs)[0]
        return [str(repo.changelog.rev(r)) for r in o]

    def getpatches(revs):
        for r in cmdutil.revrange(repo, revs):
            output = cStringIO.StringIO()
            patch.export(repo, [r], fp=output,
                         opts=patch.diffopts(ui, opts))
            yield output.getvalue().split('\n')

    def getbundle(dest):
        tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
        tmpfn = os.path.join(tmpdir, 'bundle')
        try:
            commands.bundle(ui, repo, tmpfn, dest, **opts)
            return open(tmpfn, 'rb').read()
        finally:
            try:
                os.unlink(tmpfn)
            except:
                pass
            os.rmdir(tmpdir)

    if not (opts.get('test') or opts.get('mbox')):
        # really sending
        mail.validateconfig(ui)

    if not (revs or opts.get('rev')
            or opts.get('outgoing') or opts.get('bundle')
            or opts.get('patches')):
        raise util.Abort(_('specify at least one changeset with -r or -o'))

    if opts.get('outgoing') and opts.get('bundle'):
        raise util.Abort(_("--outgoing mode always on with --bundle;"
                           " do not re-specify --outgoing"))

    if opts.get('outgoing') or opts.get('bundle'):
        if len(revs) > 1:
            raise util.Abort(_("too many destinations"))
        dest = revs and revs[0] or None
        revs = []

    if opts.get('rev'):
        if revs:
            raise util.Abort(_('use only one form to specify the revision'))
        revs = opts.get('rev')

    if opts.get('outgoing'):
        revs = outgoing(dest, opts.get('rev'))
    if opts.get('bundle'):
        opts['revs'] = revs

    # start
    if opts.get('date'):
        start_time = util.parsedate(opts.get('date'))
    else:
        start_time = util.makedate()

    def genmsgid(id):
        return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())

    def getdescription(body, sender):
        if opts.get('desc'):
            body = open(opts.get('desc')).read()
        else:
            ui.write(_('\nWrite the introductory message for the '
                       'patch series.\n\n'))
            body = ui.edit(body, sender)
        return body

    def getpatchmsgs(patches, patchnames=None):
        jumbo = []
        msgs = []

        ui.write(_('This patch series consists of %d patches.\n\n')
                 % len(patches))

        name = None
        for i, p in enumerate(patches):
            jumbo.extend(p)
            if patchnames:
                name = patchnames[i]
            msg = makepatch(ui, repo, p, opts, _charsets, i + 1,
                            len(patches), name)
            msgs.append(msg)

        if len(patches) > 1 or opts.get('intro'):
            tlen = len(str(len(patches)))

            flag = ' '.join(opts.get('flag'))
            if flag:
                subj = '[PATCH %0*d of %d %s]' % (tlen, 0, len(patches), flag)
            else:
                subj = '[PATCH %0*d of %d]' % (tlen, 0, len(patches))
            subj += ' ' + (opts.get('subject') or
                           prompt(ui, 'Subject: ', rest=subj))

            body = ''
            if opts.get('diffstat'):
                d = cdiffstat(ui, _('Final summary:\n'), jumbo)
                if d:
                    body = '\n' + d

            body = getdescription(body, sender)
            msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
            msg['Subject'] = mail.headencode(ui, subj, _charsets,
                                             opts.get('test'))

            msgs.insert(0, (msg, subj))
        return msgs

    def getbundlemsgs(bundle):
        subj = (opts.get('subject')
                or prompt(ui, 'Subject:', 'A bundle for your repository'))

        body = getdescription('', sender)
        msg = email.MIMEMultipart.MIMEMultipart()
        if body:
            msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
        datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
        datapart.set_payload(bundle)
        bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
        datapart.add_header('Content-Disposition', 'attachment',
                            filename=bundlename)
        email.Encoders.encode_base64(datapart)
        msg.attach(datapart)
        msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
        return [(msg, subj)]

    sender = (opts.get('from') or ui.config('email', 'from') or
              ui.config('patchbomb', 'from') or
              prompt(ui, 'From', ui.username()))

    # internal option used by pbranches
    patches = opts.get('patches')
    if patches:
        msgs = getpatchmsgs(patches, opts.get('patchnames'))
    elif opts.get('bundle'):
        msgs = getbundlemsgs(getbundle(dest))
    else:
        msgs = getpatchmsgs(list(getpatches(revs)))

    def getaddrs(opt, prpt=None, default=None):
        if opts.get(opt):
            return mail.addrlistencode(ui, opts.get(opt), _charsets,
                                       opts.get('test'))

        addrs = (ui.config('email', opt) or
                 ui.config('patchbomb', opt) or '')
        if not addrs and prpt:
            addrs = prompt(ui, prpt, default)

        return mail.addrlistencode(ui, [addrs], _charsets, opts.get('test'))

    to = getaddrs('to', 'To')
    cc = getaddrs('cc', 'Cc', '')
    bcc = getaddrs('bcc')

    ui.write('\n')

    parent = opts.get('in_reply_to') or None
    # angle brackets may be omitted, they're not semantically part of the msg-id
    if parent is not None:
        if not parent.startswith('<'):
            parent = '<' + parent
        if not parent.endswith('>'):
            parent += '>'

    first = True

    sender_addr = email.Utils.parseaddr(sender)[1]
    sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
    sendmail = None
    for m, subj in msgs:
        try:
            m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
        except TypeError:
            m['Message-Id'] = genmsgid('patchbomb')
        if parent:
            m['In-Reply-To'] = parent
            m['References'] = parent
        if first:
            parent = m['Message-Id']
            first = False

        m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
        m['Date'] = email.Utils.formatdate(start_time[0], localtime=True)

        start_time = (start_time[0] + 1, start_time[1])
        m['From'] = sender
        m['To'] = ', '.join(to)
        if cc:
            m['Cc']  = ', '.join(cc)
        if bcc:
            m['Bcc'] = ', '.join(bcc)
        if opts.get('test'):
            ui.status(_('Displaying '), subj, ' ...\n')
            ui.flush()
            if 'PAGER' in os.environ:
                fp = util.popen(os.environ['PAGER'], 'w')
            else:
                fp = ui
            generator = email.Generator.Generator(fp, mangle_from_=False)
            try:
                generator.flatten(m, 0)
                fp.write('\n')
            except IOError, inst:
                if inst.errno != errno.EPIPE:
                    raise
            if fp is not ui:
                fp.close()
        elif opts.get('mbox'):
            ui.status(_('Writing '), subj, ' ...\n')
            fp = open(opts.get('mbox'), 'In-Reply-To' in m and 'ab+' or 'wb+')
            generator = email.Generator.Generator(fp, mangle_from_=True)
            # Should be time.asctime(), but Windows prints 2-characters day
            # of month instead of one. Make them print the same thing.
            date = time.strftime('%a %b %d %H:%M:%S %Y',
                                 time.localtime(start_time[0]))
            fp.write('From %s %s\n' % (sender_addr, date))
            generator.flatten(m, 0)
            fp.write('\n\n')
            fp.close()
Пример #34
0
# hgversion.py - Version information for Mercurial
#
# Copyright 2009 Steve Borho <*****@*****.**>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.

import re

try:
    # post 1.1.2
    from mercurial import util
    hgversion = util.version()
except AttributeError:
    # <= 1.1.2
    from mercurial import version
    hgversion = version.get_version()

def checkhgversion(v):
    """range check the Mercurial version"""
    reqver = ['2', '6']
    v = v.split('+')[0]
    if not v or v == 'unknown' or len(v) >= 12:
        # can't make any intelligent decisions about unknown or hashes
        return
    vers = re.split(r'\.|-', v)[:2]
    if vers == reqver or len(vers) < 2:
        return
    nextver = map(str, divmod(int(reqver[0]) * 10 + int(reqver[1]) + 1, 10))
    if vers == nextver:
        return
Пример #35
0
def robustcheckout(ui,
                   url,
                   dest,
                   upstream=None,
                   revision=None,
                   branch=None,
                   purge=False,
                   sharebase=None,
                   networkattempts=None,
                   sparseprofile=None):
    """Ensure a working copy has the specified revision checked out.

    Repository data is automatically pooled into the common directory
    specified by ``--sharebase``, which is a required argument. It is required
    because pooling storage prevents excessive cloning, which makes operations
    complete faster.

    One of ``--revision`` or ``--branch`` must be specified. ``--revision``
    is preferred, as it is deterministic and there is no ambiguity as to which
    revision will actually be checked out.

    If ``--upstream`` is used, the repo at that URL is used to perform the
    initial clone instead of cloning from the repo where the desired revision
    is located.

    ``--purge`` controls whether to removed untracked and ignored files from
    the working directory. If used, the end state of the working directory
    should only contain files explicitly under version control for the requested
    revision.

    ``--sparseprofile`` can be used to specify a sparse checkout profile to use.
    The sparse checkout profile corresponds to a file in the revision to be
    checked out. If a previous sparse profile or config is present, it will be
    replaced by this sparse profile. We choose not to "widen" the sparse config
    so operations are as deterministic as possible. If an existing checkout
    is present and it isn't using a sparse checkout, we error. This is to
    prevent accidentally enabling sparse on a repository that may have
    clients that aren't sparse aware. Sparse checkout support requires Mercurial
    4.3 or newer and the ``sparse`` extension must be enabled.
    """
    if not revision and not branch:
        raise error.Abort('must specify one of --revision or --branch')

    if revision and branch:
        raise error.Abort('cannot specify both --revision and --branch')

    # Require revision to look like a SHA-1.
    if revision:
        if len(revision) < 12 or len(revision) > 40 or not re.match(
                '^[a-f0-9]+$', revision):
            raise error.Abort('--revision must be a SHA-1 fragment 12-40 '
                              'characters long')

    sharebase = sharebase or ui.config('share', 'pool')
    if not sharebase:
        raise error.Abort(
            'share base directory not defined; refusing to operate',
            hint='define share.pool config option or pass --sharebase')

    # Sparse profile support was added in Mercurial 4.3, where it was highly
    # experimental. Because of the fragility of it, we only support sparse
    # profiles on 4.3. When 4.4 is released, we'll need to opt in to sparse
    # support. We /could/ silently fall back to non-sparse when not supported.
    # However, given that sparse has performance implications, we want to fail
    # fast if we can't satisfy the desired checkout request.
    if sparseprofile:
        if not supported_hg():
            raise error.Abort(
                'sparse profile support only available for '
                'Mercurial versions greater than 4.3 (using %s)' %
                util.version())

        try:
            extensions.find('sparse')
        except KeyError:
            raise error.Abort('sparse extension must be enabled to use '
                              '--sparseprofile')

    ui.warn('(using Mercurial %s)\n' % util.version())

    # worker.backgroundclose only makes things faster if running anti-virus,
    # which our automation doesn't. Disable it.
    ui.setconfig('worker', 'backgroundclose', False)

    # By default the progress bar starts after 3s and updates every 0.1s. We
    # change this so it shows and updates every 1.0s.
    # We also tell progress to assume a TTY is present so updates are printed
    # even if there is no known TTY.
    # We make the config change here instead of in a config file because
    # otherwise we're at the whim of whatever configs are used in automation.
    ui.setconfig('progress', 'delay', 1.0)
    ui.setconfig('progress', 'refresh', 1.0)
    ui.setconfig('progress', 'assume-tty', True)

    sharebase = os.path.realpath(sharebase)

    optimes = []
    behaviors = set()
    start = time.time()

    try:
        return _docheckout(ui,
                           url,
                           dest,
                           upstream,
                           revision,
                           branch,
                           purge,
                           sharebase,
                           optimes,
                           behaviors,
                           networkattempts,
                           sparse_profile=sparseprofile)
    finally:
        overall = time.time() - start

        # We store the overall time multiple ways in order to help differentiate
        # the various "flavors" of operations.

        # ``overall`` is always the total operation time.
        optimes.append(('overall', overall))

        def record_op(name):
            # If special behaviors due to "corrupt" storage occur, we vary the
            # name to convey that.
            if 'remove-store' in behaviors:
                name += '_rmstore'
            if 'remove-wdir' in behaviors:
                name += '_rmwdir'

            optimes.append((name, overall))

        # We break out overall operations primarily by their network interaction
        # We have variants within for working directory operations.
        if 'clone' in behaviors and 'create-store' in behaviors:
            record_op('overall_clone')

            if 'sparse-update' in behaviors:
                record_op('overall_clone_sparsecheckout')
            else:
                record_op('overall_clone_fullcheckout')

        elif 'pull' in behaviors or 'clone' in behaviors:
            record_op('overall_pull')

            if 'sparse-update' in behaviors:
                record_op('overall_pull_sparsecheckout')
            else:
                record_op('overall_pull_fullcheckout')

            if 'empty-wdir' in behaviors:
                record_op('overall_pull_emptywdir')
            else:
                record_op('overall_pull_populatedwdir')

        else:
            record_op('overall_nopull')

            if 'sparse-update' in behaviors:
                record_op('overall_nopull_sparsecheckout')
            else:
                record_op('overall_nopull_fullcheckout')

            if 'empty-wdir' in behaviors:
                record_op('overall_nopull_emptywdir')
            else:
                record_op('overall_nopull_populatedwdir')

        server_url = urlparse.urlparse(url).netloc

        if 'TASKCLUSTER_INSTANCE_TYPE' in os.environ:
            perfherder = {
                'framework': {
                    'name': 'vcs',
                },
                'suites': [],
            }
            for op, duration in optimes:
                perfherder['suites'].append({
                    'name':
                    op,
                    'value':
                    duration,
                    'lowerIsBetter':
                    True,
                    'shouldAlert':
                    False,
                    'serverUrl':
                    server_url,
                    'extraOptions': [os.environ['TASKCLUSTER_INSTANCE_TYPE']],
                    'subtests': [],
                })

            ui.write('PERFHERDER_DATA: %s\n' %
                     json.dumps(perfherder, sort_keys=True))
Пример #36
0
def patchbomb(ui, repo, *revs, **opts):
    '''send changesets by email

    By default, diffs are sent in the format generated by
    :hg:`export`, one per message. The series starts with a "[PATCH 0
    of N]" introduction, which describes the series as a whole.

    Each patch email has a Subject line of "[PATCH M of N] ...", using
    the first line of the changeset description as the subject text.
    The message contains two or three parts. First, the changeset
    description.

    With the -d/--diffstat option, if the diffstat program is
    installed, the result of running diffstat on the patch is inserted.

    Finally, the patch itself, as generated by :hg:`export`.

    With the -d/--diffstat or -c/--confirm options, you will be presented
    with a final summary of all messages and asked for confirmation before
    the messages are sent.

    By default the patch is included as text in the email body for
    easy reviewing. Using the -a/--attach option will instead create
    an attachment for the patch. With -i/--inline an inline attachment
    will be created.

    With -o/--outgoing, emails will be generated for patches not found
    in the destination repository (or only those which are ancestors
    of the specified revisions if any are provided)

    With -b/--bundle, changesets are selected as for --outgoing, but a
    single email containing a binary Mercurial bundle as an attachment
    will be sent.

    With -m/--mbox, instead of previewing each patchbomb message in a
    pager or sending the messages directly, it will create a UNIX
    mailbox file with the patch emails. This mailbox file can be
    previewed with any mail user agent which supports UNIX mbox
    files.

    With -n/--test, all steps will run, but mail will not be sent.
    You will be prompted for an email recipient address, a subject and
    an introductory message describing the patches of your patchbomb.
    Then when all is done, patchbomb messages are displayed. If the
    PAGER environment variable is set, your pager will be fired up once
    for each patchbomb message, so you can verify everything is alright.

    In case email sending fails, you will find a backup of your series
    introductory message in ``.hg/last-email.txt``.

    Examples::

      hg email -r 3000          # send patch 3000 only
      hg email -r 3000 -r 3001  # send patches 3000 and 3001
      hg email -r 3000:3005     # send patches 3000 through 3005
      hg email 3000             # send patch 3000 (deprecated)

      hg email -o               # send all patches not in default
      hg email -o DEST          # send all patches not in DEST
      hg email -o -r 3000       # send all ancestors of 3000 not in default
      hg email -o -r 3000 DEST  # send all ancestors of 3000 not in DEST

      hg email -b               # send bundle of all patches not in default
      hg email -b DEST          # send bundle of all patches not in DEST
      hg email -b -r 3000       # bundle of all ancestors of 3000 not in default
      hg email -b -r 3000 DEST  # bundle of all ancestors of 3000 not in DEST

      hg email -o -m mbox &&    # generate an mbox file...
        mutt -R -f mbox         # ... and view it with mutt
      hg email -o -m mbox &&    # generate an mbox file ...
        formail -s sendmail \\   # ... and use formail to send from the mbox
          -bm -t < mbox         # ... using sendmail

    Before using this command, you will need to enable email in your
    hgrc. See the [email] section in hgrc(5) for details.
    '''

    _charsets = mail._charsets(ui)

    bundle = opts.get('bundle')
    date = opts.get('date')
    mbox = opts.get('mbox')
    outgoing = opts.get('outgoing')
    rev = opts.get('rev')
    # internal option used by pbranches
    patches = opts.get('patches')

    def getoutgoing(dest, revs):
        '''Return the revisions present locally but not in dest'''
        dest = ui.expandpath(dest or 'default-push', dest or 'default')
        dest, branches = hg.parseurl(dest)
        revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
        other = hg.peer(repo, opts, dest)
        ui.status(_('comparing with %s\n') % util.hidepassword(dest))
        common, _anyinc, _heads = discovery.findcommonincoming(repo, other)
        nodes = revs and map(repo.lookup, revs) or revs
        o = repo.changelog.findmissing(common, heads=nodes)
        if not o:
            ui.status(_("no changes found\n"))
            return []
        return [str(repo.changelog.rev(r)) for r in o]

    def getpatches(revs):
        for r in scmutil.revrange(repo, revs):
            output = cStringIO.StringIO()
            cmdutil.export(repo, [r], fp=output,
                         opts=patch.diffopts(ui, opts))
            yield output.getvalue().split('\n')

    def getbundle(dest):
        tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
        tmpfn = os.path.join(tmpdir, 'bundle')
        try:
            commands.bundle(ui, repo, tmpfn, dest, **opts)
            fp = open(tmpfn, 'rb')
            data = fp.read()
            fp.close()
            return data
        finally:
            try:
                os.unlink(tmpfn)
            except:
                pass
            os.rmdir(tmpdir)

    if not (opts.get('test') or mbox):
        # really sending
        mail.validateconfig(ui)

    if not (revs or rev or outgoing or bundle or patches):
        raise util.Abort(_('specify at least one changeset with -r or -o'))

    if outgoing and bundle:
        raise util.Abort(_("--outgoing mode always on with --bundle;"
                           " do not re-specify --outgoing"))

    if outgoing or bundle:
        if len(revs) > 1:
            raise util.Abort(_("too many destinations"))
        dest = revs and revs[0] or None
        revs = []

    if rev:
        if revs:
            raise util.Abort(_('use only one form to specify the revision'))
        revs = rev

    if outgoing:
        revs = getoutgoing(dest, rev)
    if bundle:
        opts['revs'] = revs

    # start
    if date:
        start_time = util.parsedate(date)
    else:
        start_time = util.makedate()

    def genmsgid(id):
        return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())

    def getdescription(body, sender):
        if opts.get('desc'):
            body = open(opts.get('desc')).read()
        else:
            ui.write(_('\nWrite the introductory message for the '
                       'patch series.\n\n'))
            body = ui.edit(body, sender)
            # Save series description in case sendmail fails
            msgfile = repo.opener('last-email.txt', 'wb')
            msgfile.write(body)
            msgfile.close()
        return body

    def getpatchmsgs(patches, patchnames=None):
        msgs = []

        ui.write(_('This patch series consists of %d patches.\n\n')
                 % len(patches))

        # build the intro message, or skip it if the user declines
        if introwanted(opts, len(patches)):
            msg = makeintro(patches)
            if msg:
                msgs.append(msg)

        # are we going to send more than one message?
        numbered = len(msgs) + len(patches) > 1

        # now generate the actual patch messages
        name = None
        for i, p in enumerate(patches):
            if patchnames:
                name = patchnames[i]
            msg = makepatch(ui, repo, p, opts, _charsets, i + 1,
                            len(patches), numbered, name)
            msgs.append(msg)

        return msgs

    def makeintro(patches):
        tlen = len(str(len(patches)))

        flag = opts.get('flag') or ''
        if flag:
            flag = ' ' + ' '.join(flag)
        prefix = '[PATCH %0*d of %d%s]' % (tlen, 0, len(patches), flag)

        subj = (opts.get('subject') or
                prompt(ui, 'Subject: ', rest=prefix, default=''))
        if not subj:
            return None         # skip intro if the user doesn't bother

        subj = prefix + ' ' + subj

        body = ''
        if opts.get('diffstat'):
            # generate a cumulative diffstat of the whole patch series
            diffstat = patch.diffstat(sum(patches, []))
            body = '\n' + diffstat
        else:
            diffstat = None

        body = getdescription(body, sender)
        msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
        msg['Subject'] = mail.headencode(ui, subj, _charsets,
                                         opts.get('test'))
        return (msg, subj, diffstat)

    def getbundlemsgs(bundle):
        subj = (opts.get('subject')
                or prompt(ui, 'Subject:', 'A bundle for your repository'))

        body = getdescription('', sender)
        msg = email.MIMEMultipart.MIMEMultipart()
        if body:
            msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
        datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
        datapart.set_payload(bundle)
        bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
        datapart.add_header('Content-Disposition', 'attachment',
                            filename=bundlename)
        email.Encoders.encode_base64(datapart)
        msg.attach(datapart)
        msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
        return [(msg, subj, None)]

    sender = (opts.get('from') or ui.config('email', 'from') or
              ui.config('patchbomb', 'from') or
              prompt(ui, 'From', ui.username()))

    if patches:
        msgs = getpatchmsgs(patches, opts.get('patchnames'))
    elif bundle:
        msgs = getbundlemsgs(getbundle(dest))
    else:
        msgs = getpatchmsgs(list(getpatches(revs)))

    showaddrs = []

    def getaddrs(header, ask=False, default=None):
        configkey = header.lower()
        opt = header.replace('-', '_').lower()
        addrs = opts.get(opt)
        if addrs:
            showaddrs.append('%s: %s' % (header, ', '.join(addrs)))
            return mail.addrlistencode(ui, addrs, _charsets, opts.get('test'))

        # not on the command line: fallback to config and then maybe ask
        addr = (ui.config('email', configkey) or
                ui.config('patchbomb', configkey) or
                '')
        if not addr and ask:
            addr = prompt(ui, header, default=default)
        if addr:
            showaddrs.append('%s: %s' % (header, addr))
            return mail.addrlistencode(ui, [addr], _charsets, opts.get('test'))
        else:
            return default

    to = getaddrs('To', ask=True)
    if not to:
        # we can get here in non-interactive mode
        raise util.Abort(_('no recipient addresses provided'))
    cc = getaddrs('Cc', ask=True, default='') or []
    bcc = getaddrs('Bcc') or []
    replyto = getaddrs('Reply-To')

    if opts.get('diffstat') or opts.get('confirm'):
        ui.write(_('\nFinal summary:\n\n'))
        ui.write('From: %s\n' % sender)
        for addr in showaddrs:
            ui.write('%s\n' % addr)
        for m, subj, ds in msgs:
            ui.write('Subject: %s\n' % subj)
            if ds:
                ui.write(ds)
        ui.write('\n')
        if ui.promptchoice(_('are you sure you want to send (yn)?'),
                           (_('&Yes'), _('&No'))):
            raise util.Abort(_('patchbomb canceled'))

    ui.write('\n')

    parent = opts.get('in_reply_to') or None
    # angle brackets may be omitted, they're not semantically part of the msg-id
    if parent is not None:
        if not parent.startswith('<'):
            parent = '<' + parent
        if not parent.endswith('>'):
            parent += '>'

    first = True

    sender_addr = email.Utils.parseaddr(sender)[1]
    sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
    sendmail = None
    for i, (m, subj, ds) in enumerate(msgs):
        try:
            m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
        except TypeError:
            m['Message-Id'] = genmsgid('patchbomb')
        if parent:
            m['In-Reply-To'] = parent
            m['References'] = parent
        if first:
            parent = m['Message-Id']
            first = False

        m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
        m['Date'] = email.Utils.formatdate(start_time[0], localtime=True)

        start_time = (start_time[0] + 1, start_time[1])
        m['From'] = sender
        m['To'] = ', '.join(to)
        if cc:
            m['Cc']  = ', '.join(cc)
        if bcc:
            m['Bcc'] = ', '.join(bcc)
        if replyto:
            m['Reply-To'] = ', '.join(replyto)
        if opts.get('test'):
            ui.status(_('Displaying '), subj, ' ...\n')
            ui.flush()
            if 'PAGER' in os.environ and not ui.plain():
                fp = util.popen(os.environ['PAGER'], 'w')
            else:
                fp = ui
            generator = email.Generator.Generator(fp, mangle_from_=False)
            try:
                generator.flatten(m, 0)
                fp.write('\n')
            except IOError, inst:
                if inst.errno != errno.EPIPE:
                    raise
            if fp is not ui:
                fp.close()
        else:
            if not sendmail:
                sendmail = mail.connect(ui, mbox=mbox)
            ui.status(_('Sending '), subj, ' ...\n')
            ui.progress(_('sending'), i, item=subj, total=len(msgs))
            if not mbox:
                # Exim does not remove the Bcc field
                del m['Bcc']
            fp = cStringIO.StringIO()
            generator = email.Generator.Generator(fp, mangle_from_=False)
            generator.flatten(m, 0)
            sendmail(sender_addr, to + bcc + cc, fp.getvalue())
Пример #37
0
def robustcheckout(ui,
                   url,
                   dest,
                   upstream=None,
                   revision=None,
                   branch=None,
                   purge=False,
                   sharebase=None,
                   networkattempts=None,
                   sparseprofile=None):
    """Ensure a working copy has the specified revision checked out.

    Repository data is automatically pooled into the common directory
    specified by ``--sharebase``, which is a required argument. It is required
    because pooling storage prevents excessive cloning, which makes operations
    complete faster.

    One of ``--revision`` or ``--branch`` must be specified. ``--revision``
    is preferred, as it is deterministic and there is no ambiguity as to which
    revision will actually be checked out.

    If ``--upstream`` is used, the repo at that URL is used to perform the
    initial clone instead of cloning from the repo where the desired revision
    is located.

    ``--purge`` controls whether to removed untracked and ignored files from
    the working directory. If used, the end state of the working directory
    should only contain files explicitly under version control for the requested
    revision.

    ``--sparseprofile`` can be used to specify a sparse checkout profile to use.
    The sparse checkout profile corresponds to a file in the revision to be
    checked out. If a previous sparse profile or config is present, it will be
    replaced by this sparse profile. We choose not to "widen" the sparse config
    so operations are as deterministic as possible. If an existing checkout
    is present and it isn't using a sparse checkout, we error. This is to
    prevent accidentally enabling sparse on a repository that may have
    clients that aren't sparse aware. Sparse checkout support requires Mercurial
    4.3 or newer and the ``sparse`` extension must be enabled.
    """
    if not revision and not branch:
        raise error.Abort('must specify one of --revision or --branch')

    if revision and branch:
        raise error.Abort('cannot specify both --revision and --branch')

    # Require revision to look like a SHA-1.
    if revision:
        if len(revision) < 12 or len(revision) > 40 or not re.match(
                '^[a-f0-9]+$', revision):
            raise error.Abort('--revision must be a SHA-1 fragment 12-40 '
                              'characters long')

    sharebase = sharebase or ui.config('share', 'pool')
    if not sharebase:
        raise error.Abort(
            'share base directory not defined; refusing to operate',
            hint='define share.pool config option or pass --sharebase')

    # Sparse profile support was added in Mercurial 4.3, where it was highly
    # experimental. Because of the fragility of it, we only support sparse
    # profiles on 4.3. When 4.4 is released, we'll need to opt in to sparse
    # support. We /could/ silently fall back to non-sparse when not supported.
    # However, given that sparse has performance implications, we want to fail
    # fast if we can't satisfy the desired checkout request.
    if sparseprofile:
        if util.versiontuple(n=2) not in ((4, 3), (4, 4), (4, 5)):
            raise error.Abort(
                'sparse profile support only available for '
                'Mercurial versions greater than 4.3 (using %s)' %
                util.version())

        try:
            extensions.find('sparse')
        except KeyError:
            raise error.Abort('sparse extension must be enabled to use '
                              '--sparseprofile')

    ui.warn('(using Mercurial %s)\n' % util.version())

    # worker.backgroundclose only makes things faster if running anti-virus,
    # which our automation doesn't. Disable it.
    ui.setconfig('worker', 'backgroundclose', False)

    # By default the progress bar starts after 3s and updates every 0.1s. We
    # change this so it shows and updates every 1.0s.
    # We also tell progress to assume a TTY is present so updates are printed
    # even if there is no known TTY.
    # We make the config change here instead of in a config file because
    # otherwise we're at the whim of whatever configs are used in automation.
    ui.setconfig('progress', 'delay', 1.0)
    ui.setconfig('progress', 'refresh', 1.0)
    ui.setconfig('progress', 'assume-tty', True)

    sharebase = os.path.realpath(sharebase)

    return _docheckout(ui,
                       url,
                       dest,
                       upstream,
                       revision,
                       branch,
                       purge,
                       sharebase,
                       networkattempts,
                       sparse_profile=sparseprofile)
def email(ui, repo, *revs, **opts):
    '''send changesets by email

    By default, diffs are sent in the format generated by
    :hg:`export`, one per message. The series starts with a "[PATCH 0
    of N]" introduction, which describes the series as a whole.

    Each patch email has a Subject line of "[PATCH M of N] ...", using
    the first line of the changeset description as the subject text.
    The message contains two or three parts. First, the changeset
    description.

    With the -d/--diffstat option, if the diffstat program is
    installed, the result of running diffstat on the patch is inserted.

    Finally, the patch itself, as generated by :hg:`export`.

    With the -d/--diffstat or --confirm options, you will be presented
    with a final summary of all messages and asked for confirmation before
    the messages are sent.

    By default the patch is included as text in the email body for
    easy reviewing. Using the -a/--attach option will instead create
    an attachment for the patch. With -i/--inline an inline attachment
    will be created. You can include a patch both as text in the email
    body and as a regular or an inline attachment by combining the
    -a/--attach or -i/--inline with the --body option.

    With -B/--bookmark changesets reachable by the given bookmark are
    selected.

    With -o/--outgoing, emails will be generated for patches not found
    in the destination repository (or only those which are ancestors
    of the specified revisions if any are provided)

    With -b/--bundle, changesets are selected as for --outgoing, but a
    single email containing a binary Mercurial bundle as an attachment
    will be sent. Use the ``patchbomb.bundletype`` config option to
    control the bundle type as with :hg:`bundle --type`.

    With -m/--mbox, instead of previewing each patchbomb message in a
    pager or sending the messages directly, it will create a UNIX
    mailbox file with the patch emails. This mailbox file can be
    previewed with any mail user agent which supports UNIX mbox
    files.

    With -n/--test, all steps will run, but mail will not be sent.
    You will be prompted for an email recipient address, a subject and
    an introductory message describing the patches of your patchbomb.
    Then when all is done, patchbomb messages are displayed.

    In case email sending fails, you will find a backup of your series
    introductory message in ``.hg/last-email.txt``.

    The default behavior of this command can be customized through
    configuration. (See :hg:`help patchbomb` for details)

    Examples::

      hg email -r 3000          # send patch 3000 only
      hg email -r 3000 -r 3001  # send patches 3000 and 3001
      hg email -r 3000:3005     # send patches 3000 through 3005
      hg email 3000             # send patch 3000 (deprecated)

      hg email -o               # send all patches not in default
      hg email -o DEST          # send all patches not in DEST
      hg email -o -r 3000       # send all ancestors of 3000 not in default
      hg email -o -r 3000 DEST  # send all ancestors of 3000 not in DEST

      hg email -B feature       # send all ancestors of feature bookmark

      hg email -b               # send bundle of all patches not in default
      hg email -b DEST          # send bundle of all patches not in DEST
      hg email -b -r 3000       # bundle of all ancestors of 3000 not in default
      hg email -b -r 3000 DEST  # bundle of all ancestors of 3000 not in DEST

      hg email -o -m mbox &&    # generate an mbox file...
        mutt -R -f mbox         # ... and view it with mutt
      hg email -o -m mbox &&    # generate an mbox file ...
        formail -s sendmail \\   # ... and use formail to send from the mbox
          -bm -t < mbox         # ... using sendmail

    Before using this command, you will need to enable email in your
    hgrc. See the [email] section in hgrc(5) for details.
    '''

    _charsets = mail._charsets(ui)

    bundle = opts.get('bundle')
    date = opts.get('date')
    mbox = opts.get('mbox')
    outgoing = opts.get('outgoing')
    rev = opts.get('rev')
    bookmark = opts.get('bookmark')

    if not (opts.get('test') or mbox):
        # really sending
        mail.validateconfig(ui)

    if not (revs or rev or outgoing or bundle or bookmark):
        raise error.Abort(
            _('specify at least one changeset with -B, -r or -o'))

    if outgoing and bundle:
        raise error.Abort(
            _("--outgoing mode always on with --bundle;"
              " do not re-specify --outgoing"))
    if rev and bookmark:
        raise error.Abort(_("-r and -B are mutually exclusive"))

    if outgoing or bundle:
        if len(revs) > 1:
            raise error.Abort(_("too many destinations"))
        if revs:
            dest = revs[0]
        else:
            dest = None
        revs = []

    if rev:
        if revs:
            raise error.Abort(_('use only one form to specify the revision'))
        revs = rev
    elif bookmark:
        if bookmark not in repo._bookmarks:
            raise error.Abort(_("bookmark '%s' not found") % bookmark)
        revs = repair.stripbmrevset(repo, bookmark)

    revs = scmutil.revrange(repo, revs)
    if outgoing:
        revs = _getoutgoing(repo, dest, revs)
    if bundle:
        opts['revs'] = [str(r) for r in revs]

    # check if revision exist on the public destination
    publicurl = repo.ui.config('patchbomb', 'publicurl')
    if publicurl:
        repo.ui.debug('checking that revision exist in the public repo')
        try:
            publicpeer = hg.peer(repo, {}, publicurl)
        except error.RepoError:
            repo.ui.write_err(
                _('unable to access public repo: %s\n') % publicurl)
            raise
        if not publicpeer.capable('known'):
            repo.ui.debug('skipping existence checks: public repo too old')
        else:
            out = [repo[r] for r in revs]
            known = publicpeer.known(h.node() for h in out)
            missing = []
            for idx, h in enumerate(out):
                if not known[idx]:
                    missing.append(h)
            if missing:
                if 1 < len(missing):
                    msg = _('public "%s" is missing %s and %i others')
                    msg %= (publicurl, missing[0], len(missing) - 1)
                else:
                    msg = _('public url %s is missing %s')
                    msg %= (publicurl, missing[0])
                revhint = ' '.join('-r %s' % h
                                   for h in repo.set('heads(%ld)', missing))
                hint = _("use 'hg push %s %s'") % (publicurl, revhint)
                raise error.Abort(msg, hint=hint)

    # start
    if date:
        start_time = util.parsedate(date)
    else:
        start_time = util.makedate()

    def genmsgid(id):
        return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())

    # deprecated config: patchbomb.from
    sender = (opts.get('from') or ui.config('email', 'from')
              or ui.config('patchbomb', 'from')
              or prompt(ui, 'From', ui.username()))

    if bundle:
        bundledata = _getbundle(repo, dest, **opts)
        bundleopts = opts.copy()
        bundleopts.pop('bundle', None)  # already processed
        msgs = _getbundlemsgs(repo, sender, bundledata, **bundleopts)
    else:
        msgs = _getpatchmsgs(repo, sender, revs, **opts)

    showaddrs = []

    def getaddrs(header, ask=False, default=None):
        configkey = header.lower()
        opt = header.replace('-', '_').lower()
        addrs = opts.get(opt)
        if addrs:
            showaddrs.append('%s: %s' % (header, ', '.join(addrs)))
            return mail.addrlistencode(ui, addrs, _charsets, opts.get('test'))

        # not on the command line: fallback to config and then maybe ask
        addr = (ui.config('email', configkey)
                or ui.config('patchbomb', configkey))
        if not addr:
            specified = (ui.hasconfig('email', configkey)
                         or ui.hasconfig('patchbomb', configkey))
            if not specified and ask:
                addr = prompt(ui, header, default=default)
        if addr:
            showaddrs.append('%s: %s' % (header, addr))
            return mail.addrlistencode(ui, [addr], _charsets, opts.get('test'))
        elif default:
            return mail.addrlistencode(ui, [default], _charsets,
                                       opts.get('test'))
        return []

    to = getaddrs('To', ask=True)
    if not to:
        # we can get here in non-interactive mode
        raise error.Abort(_('no recipient addresses provided'))
    cc = getaddrs('Cc', ask=True, default='')
    bcc = getaddrs('Bcc')
    replyto = getaddrs('Reply-To')

    confirm = ui.configbool('patchbomb', 'confirm')
    confirm |= bool(opts.get('diffstat') or opts.get('confirm'))

    if confirm:
        ui.write(_('\nFinal summary:\n\n'), label='patchbomb.finalsummary')
        ui.write(('From: %s\n' % sender), label='patchbomb.from')
        for addr in showaddrs:
            ui.write('%s\n' % addr, label='patchbomb.to')
        for m, subj, ds in msgs:
            ui.write(('Subject: %s\n' % subj), label='patchbomb.subject')
            if ds:
                ui.write(ds, label='patchbomb.diffstats')
        ui.write('\n')
        if ui.promptchoice(
                _('are you sure you want to send (yn)?'
                  '$$ &Yes $$ &No')):
            raise error.Abort(_('patchbomb canceled'))

    ui.write('\n')

    parent = opts.get('in_reply_to') or None
    # angle brackets may be omitted, they're not semantically part of the msg-id
    if parent is not None:
        if not parent.startswith('<'):
            parent = '<' + parent
        if not parent.endswith('>'):
            parent += '>'

    sender_addr = emailmod.Utils.parseaddr(sender)[1]
    sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
    sendmail = None
    firstpatch = None
    for i, (m, subj, ds) in enumerate(msgs):
        try:
            m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
            if not firstpatch:
                firstpatch = m['Message-Id']
            m['X-Mercurial-Series-Id'] = firstpatch
        except TypeError:
            m['Message-Id'] = genmsgid('patchbomb')
        if parent:
            m['In-Reply-To'] = parent
            m['References'] = parent
        if not parent or 'X-Mercurial-Node' not in m:
            parent = m['Message-Id']

        m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
        m['Date'] = emailmod.Utils.formatdate(start_time[0], localtime=True)

        start_time = (start_time[0] + 1, start_time[1])
        m['From'] = sender
        m['To'] = ', '.join(to)
        if cc:
            m['Cc'] = ', '.join(cc)
        if bcc:
            m['Bcc'] = ', '.join(bcc)
        if replyto:
            m['Reply-To'] = ', '.join(replyto)
        if opts.get('test'):
            ui.status(_('displaying '), subj, ' ...\n')
            ui.pager('email')
            generator = emailmod.Generator.Generator(ui, mangle_from_=False)
            try:
                generator.flatten(m, 0)
                ui.write('\n')
            except IOError as inst:
                if inst.errno != errno.EPIPE:
                    raise
        else:
            if not sendmail:
                sendmail = mail.connect(ui, mbox=mbox)
            ui.status(_('sending '), subj, ' ...\n')
            ui.progress(_('sending'),
                        i,
                        item=subj,
                        total=len(msgs),
                        unit=_('emails'))
            if not mbox:
                # Exim does not remove the Bcc field
                del m['Bcc']
            fp = stringio()
            generator = emailmod.Generator.Generator(fp, mangle_from_=False)
            generator.flatten(m, 0)
            sendmail(sender_addr, to + bcc + cc, fp.getvalue())

    ui.progress(_('writing'), None)
    ui.progress(_('sending'), None)
Пример #39
0
        def clone(self, remote, heads=[], stream=False):
            supported = True

            if (exchange and hasattr(exchange, '_maybeapplyclonebundle')
                    and remote.capable('clonebundles')):
                supported = False
                self.ui.warn(
                    _('(mercurial client has built-in support for '
                      'bundle clone features; the "bundleclone" '
                      'extension can likely safely be removed)\n'))

                if not self.ui.configbool('experimental', 'clonebundles',
                                          False):
                    self.ui.warn(
                        _('(but the experimental.clonebundles config '
                          'flag is not enabled: enable it before '
                          'disabling bundleclone or cloning from '
                          'pre-generated bundles may not work)\n'))
                    # We assume that presence of the bundleclone extension
                    # means they want clonebundles enabled. Otherwise, why do
                    # they have bundleclone enabled? So silently enable it.
                    ui.setconfig('experimental', 'clonebundles', True)
            elif not remote.capable('bundles'):
                supported = False
                self.ui.debug(_('bundle clone not supported\n'))
            elif heads:
                supported = False
                self.ui.debug(
                    _('cannot perform bundle clone if heads requested\n'))
            elif stream:
                supported = False
                self.ui.debug(
                    _('ignoring bundle clone because stream was '
                      'requested\n'))

            if not supported:
                return super(bundleclonerepo, self).clone(remote,
                                                          heads=heads,
                                                          stream=stream)

            result = remote._call('bundles')

            if not result:
                self.ui.note(_('no bundles available; using normal clone\n'))
                return super(bundleclonerepo, self).clone(remote,
                                                          heads=heads,
                                                          stream=stream)

            pyver = sys.version_info
            pyver = (pyver[0], pyver[1], pyver[2])

            hgver = util.version()
            # Discard bit after '+'.
            hgver = hgver.split('+')[0]
            try:
                hgver = tuple([int(i) for i in hgver.split('.')[0:2]])
            except ValueError:
                hgver = (0, 0)

            # Testing backdoors.
            if ui.config('bundleclone', 'fakepyver'):
                pyver = ui.configlist('bundleclone', 'fakepyver')
                pyver = tuple(int(v) for v in pyver)

            if ui.config('bundleclone', 'fakehgver'):
                hgver = ui.configlist('bundleclone', 'fakehgver')
                hgver = tuple(int(v) for v in hgver[0:2])

            entries = []
            snifilteredfrompython = False
            snifilteredfromhg = False

            for line in result.splitlines():
                fields = line.split()
                url = fields[0]
                attrs = {}
                for rawattr in fields[1:]:
                    key, value = rawattr.split('=', 1)
                    attrs[urllib.unquote(key)] = urllib.unquote(value)

                # Filter out SNI entries if we don't support SNI.
                if attrs.get('requiresni') == 'true':
                    skip = False
                    if pyver < (2, 7, 9):
                        # Take this opportunity to inform people they are using an
                        # old, insecure Python.
                        if not snifilteredfrompython:
                            self.ui.warn(
                                _('(your Python is older than 2.7.9 '
                                  'and does not support modern and '
                                  'secure SSL/TLS; please consider '
                                  'upgrading your Python to a secure '
                                  'version)\n'))
                        snifilteredfrompython = True
                        skip = True

                    if hgver < (3, 3):
                        if not snifilteredfromhg:
                            self.ui.warn(
                                _('(you Mercurial is old and does '
                                  'not support modern and secure '
                                  'SSL/TLS; please consider '
                                  'upgrading your Mercurial to 3.3+ '
                                  'which supports modern and secure '
                                  'SSL/TLS)\n'))
                        snifilteredfromhg = True
                        skip = True

                    if skip:
                        self.ui.warn(
                            _('(ignoring URL on server that requires '
                              'SNI)\n'))
                        continue

                entries.append((url, attrs))

            if not entries:
                # Don't fall back to normal clone because we don't want mass
                # fallback in the wild to barage servers expecting bundle
                # offload.
                raise util.Abort(_('no appropriate bundles available'),
                                 hint=_('you may wish to complain to the '
                                        'server operator'))

            # The configuration is allowed to define lists of preferred
            # attributes and values. If this is present, sort results according
            # to that preference. Otherwise, use manifest order and select the
            # first entry.
            prefers = self.ui.configlist('bundleclone', 'prefers', default=[])
            if prefers:
                prefers = [p.split('=', 1) for p in prefers]

                def compareentry(a, b):
                    aattrs = a[1]
                    battrs = b[1]

                    # Itereate over local preferences.
                    for pkey, pvalue in prefers:
                        avalue = aattrs.get(pkey)
                        bvalue = battrs.get(pkey)

                        # Special case for b is missing attribute and a matches
                        # exactly.
                        if avalue is not None and bvalue is None and avalue == pvalue:
                            return -1

                        # Special case for a missing attribute and b matches
                        # exactly.
                        if bvalue is not None and avalue is None and bvalue == pvalue:
                            return 1

                        # We can't compare unless the attribute is defined on
                        # both entries.
                        if avalue is None or bvalue is None:
                            continue

                        # Same values should fall back to next attribute.
                        if avalue == bvalue:
                            continue

                        # Exact matches come first.
                        if avalue == pvalue:
                            return -1
                        if bvalue == pvalue:
                            return 1

                        # Fall back to next attribute.
                        continue

                    # Entries could not be sorted based on attributes. This
                    # says they are equal, which will fall back to index order,
                    # which is what we want.
                    return 0

                entries = sorted(entries, cmp=compareentry)

            url, attrs = entries[0]

            if not url:
                self.ui.note(
                    _('invalid bundle manifest; using normal clone\n'))
                return super(bundleclonerepo, self).clone(remote,
                                                          heads=heads,
                                                          stream=stream)

            self.ui.status(_('downloading bundle %s\n' % url))

            try:
                fh = hgurl.open(self.ui, url)
                # Stream clone data is not changegroup data. Handle it
                # specially.
                if 'stream' in attrs:
                    reqs = set(attrs['stream'].split(','))
                    l = fh.readline()
                    filecount, bytecount = map(int, l.split(' ', 1))
                    self.ui.status(_('streaming all changes\n'))
                    consumev1(self, fh, filecount, bytecount)
                else:
                    if exchange:
                        cg = exchange.readbundle(self.ui, fh, 'stream')
                    else:
                        cg = changegroup.readbundle(fh, 'stream')

                    # Mercurial 3.6 introduced cgNunpacker.apply().
                    # Before that, there was changegroup.addchangegroup().
                    # Before that, there was localrepository.addchangegroup().
                    if hasattr(cg, 'apply'):
                        cg.apply(self, 'bundleclone', url)
                    elif hasattr(changegroup, 'addchangegroup'):
                        changegroup.addchangegroup(self, cg, 'bundleclone',
                                                   url)
                    else:
                        self.addchangegroup(cg, 'bundleclone', url)

                self.ui.status(_('finishing applying bundle; pulling\n'))
                # Maintain compatibility with Mercurial 2.x.
                if exchange:
                    return exchange.pull(self, remote, heads=heads)
                else:
                    return self.pull(remote, heads=heads)

            except (urllib2.HTTPError, urllib2.URLError) as e:
                if isinstance(e, urllib2.HTTPError):
                    msg = _('HTTP error fetching bundle: %s') % str(e)
                else:
                    msg = _('error fetching bundle: %s') % e.reason

                # Don't fall back to regular clone unless explicitly told to.
                if not self.ui.configbool('bundleclone', 'fallbackonerror',
                                          False):
                    raise util.Abort(
                        msg,
                        hint=_('consider contacting the '
                               'server operator if this error persists'))

                self.ui.warn(msg + '\n')
                self.ui.warn(_('falling back to normal clone\n'))

                return super(bundleclonerepo, self).clone(remote,
                                                          heads=heads,
                                                          stream=stream)
Пример #40
0
Usage: activate the extension in your hgrc file::

    [extensions]
    hgcodesmell = path/to/hgcodesmell.py

Then, when "smelly" changes would be committed, they will be printed out and you
will be prompted whether to actually do the commit.
"""

import os
import re
import fnmatch

from mercurial import commands, cmdutil, extensions, patch, util

if util.version() >= '1.9':
    # cmdutil.match and cmdutil.revpair moved to scmutil
    from mercurial import scmutil
    utilmodule = scmutil
else:
    utilmodule = cmdutil

try:
    # use the color extension to render diffs, if it is recent enough
    from hgext import color
    if hasattr(color, 'colorwrap'):

        def write_colored(ui, diff):
            color.colorwrap(ui.write, ''.join(diff))
    elif hasattr(color, 'render_effects'):
        # hg 1.6: different API
Пример #41
0
# hgversion.py - Version information for Mercurial
#
# Copyright 2009 Steve Borho <*****@*****.**>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.

import re

try:
    # post 1.1.2
    from mercurial import util
    hgversion = util.version()
except AttributeError:
    # <= 1.1.2
    from mercurial import version
    hgversion = version.get_version()


def checkhgversion(v):
    """range check the Mercurial version"""
    reqver = ['2', '6']
    v = v.split('+')[0]
    if not v or v == 'unknown' or len(v) >= 12:
        # can't make any intelligent decisions about unknown or hashes
        return
    vers = re.split(r'\.|-', v)[:2]
    if vers == reqver or len(vers) < 2:
        return
    nextver = map(str, divmod(int(reqver[0]) * 10 + int(reqver[1]) + 1, 10))
    if vers == nextver:
Пример #42
0
 def _vcs_version(self):
     if version == None:
         return None
     return version()
Пример #43
0
def patchbomb(ui, repo, *revs, **opts):
    '''send changesets by email

    By default, diffs are sent in the format generated by
    :hg:`export`, one per message. The series starts with a "[PATCH 0
    of N]" introduction, which describes the series as a whole.

    Each patch email has a Subject line of "[PATCH M of N] ...", using
    the first line of the changeset description as the subject text.
    The message contains two or three parts. First, the changeset
    description.

    With the -d/--diffstat option, if the diffstat program is
    installed, the result of running diffstat on the patch is inserted.

    Finally, the patch itself, as generated by :hg:`export`.

    With the -d/--diffstat or -c/--confirm options, you will be presented
    with a final summary of all messages and asked for confirmation before
    the messages are sent.

    By default the patch is included as text in the email body for
    easy reviewing. Using the -a/--attach option will instead create
    an attachment for the patch. With -i/--inline an inline attachment
    will be created.

    With -o/--outgoing, emails will be generated for patches not found
    in the destination repository (or only those which are ancestors
    of the specified revisions if any are provided)

    With -b/--bundle, changesets are selected as for --outgoing, but a
    single email containing a binary Mercurial bundle as an attachment
    will be sent.

    With -m/--mbox, instead of previewing each patchbomb message in a
    pager or sending the messages directly, it will create a UNIX
    mailbox file with the patch emails. This mailbox file can be
    previewed with any mail user agent which supports UNIX mbox
    files.

    With -n/--test, all steps will run, but mail will not be sent.
    You will be prompted for an email recipient address, a subject and
    an introductory message describing the patches of your patchbomb.
    Then when all is done, patchbomb messages are displayed. If the
    PAGER environment variable is set, your pager will be fired up once
    for each patchbomb message, so you can verify everything is alright.

    In case email sending fails, you will find a backup of your series
    introductory message in ``.hg/last-email.txt``.

    Examples::

      hg email -r 3000          # send patch 3000 only
      hg email -r 3000 -r 3001  # send patches 3000 and 3001
      hg email -r 3000:3005     # send patches 3000 through 3005
      hg email 3000             # send patch 3000 (deprecated)

      hg email -o               # send all patches not in default
      hg email -o DEST          # send all patches not in DEST
      hg email -o -r 3000       # send all ancestors of 3000 not in default
      hg email -o -r 3000 DEST  # send all ancestors of 3000 not in DEST

      hg email -b               # send bundle of all patches not in default
      hg email -b DEST          # send bundle of all patches not in DEST
      hg email -b -r 3000       # bundle of all ancestors of 3000 not in default
      hg email -b -r 3000 DEST  # bundle of all ancestors of 3000 not in DEST

      hg email -o -m mbox &&    # generate an mbox file...
        mutt -R -f mbox         # ... and view it with mutt
      hg email -o -m mbox &&    # generate an mbox file ...
        formail -s sendmail \\   # ... and use formail to send from the mbox
          -bm -t < mbox         # ... using sendmail

    Before using this command, you will need to enable email in your
    hgrc. See the [email] section in hgrc(5) for details.
    '''

    _charsets = mail._charsets(ui)

    bundle = opts.get('bundle')
    date = opts.get('date')
    mbox = opts.get('mbox')
    outgoing = opts.get('outgoing')
    rev = opts.get('rev')
    # internal option used by pbranches
    patches = opts.get('patches')

    def getoutgoing(dest, revs):
        '''Return the revisions present locally but not in dest'''
        dest = ui.expandpath(dest or 'default-push', dest or 'default')
        dest, branches = hg.parseurl(dest)
        revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
        other = hg.peer(repo, opts, dest)
        ui.status(_('comparing with %s\n') % util.hidepassword(dest))
        common, _anyinc, _heads = discovery.findcommonincoming(repo, other)
        nodes = revs and map(repo.lookup, revs) or revs
        o = repo.changelog.findmissing(common, heads=nodes)
        if not o:
            ui.status(_("no changes found\n"))
            return []
        return [str(repo.changelog.rev(r)) for r in o]

    def getpatches(revs):
        for r in scmutil.revrange(repo, revs):
            output = cStringIO.StringIO()
            cmdutil.export(repo, [r], fp=output, opts=patch.diffopts(ui, opts))
            yield output.getvalue().split('\n')

    def getbundle(dest):
        tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
        tmpfn = os.path.join(tmpdir, 'bundle')
        try:
            commands.bundle(ui, repo, tmpfn, dest, **opts)
            fp = open(tmpfn, 'rb')
            data = fp.read()
            fp.close()
            return data
        finally:
            try:
                os.unlink(tmpfn)
            except:
                pass
            os.rmdir(tmpdir)

    if not (opts.get('test') or mbox):
        # really sending
        mail.validateconfig(ui)

    if not (revs or rev or outgoing or bundle or patches):
        raise util.Abort(_('specify at least one changeset with -r or -o'))

    if outgoing and bundle:
        raise util.Abort(
            _("--outgoing mode always on with --bundle;"
              " do not re-specify --outgoing"))

    if outgoing or bundle:
        if len(revs) > 1:
            raise util.Abort(_("too many destinations"))
        dest = revs and revs[0] or None
        revs = []

    if rev:
        if revs:
            raise util.Abort(_('use only one form to specify the revision'))
        revs = rev

    if outgoing:
        revs = getoutgoing(dest, rev)
    if bundle:
        opts['revs'] = revs

    # start
    if date:
        start_time = util.parsedate(date)
    else:
        start_time = util.makedate()

    def genmsgid(id):
        return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())

    def getdescription(body, sender):
        if opts.get('desc'):
            body = open(opts.get('desc')).read()
        else:
            ui.write(
                _('\nWrite the introductory message for the '
                  'patch series.\n\n'))
            body = ui.edit(body, sender)
            # Save series description in case sendmail fails
            msgfile = repo.opener('last-email.txt', 'wb')
            msgfile.write(body)
            msgfile.close()
        return body

    def getpatchmsgs(patches, patchnames=None):
        msgs = []

        ui.write(
            _('This patch series consists of %d patches.\n\n') % len(patches))

        # build the intro message, or skip it if the user declines
        if introwanted(opts, len(patches)):
            msg = makeintro(patches)
            if msg:
                msgs.append(msg)

        # are we going to send more than one message?
        numbered = len(msgs) + len(patches) > 1

        # now generate the actual patch messages
        name = None
        for i, p in enumerate(patches):
            if patchnames:
                name = patchnames[i]
            msg = makepatch(ui, repo, p, opts, _charsets, i + 1, len(patches),
                            numbered, name)
            msgs.append(msg)

        return msgs

    def makeintro(patches):
        tlen = len(str(len(patches)))

        flag = opts.get('flag') or ''
        if flag:
            flag = ' ' + ' '.join(flag)
        prefix = '[PATCH %0*d of %d%s]' % (tlen, 0, len(patches), flag)

        subj = (opts.get('subject')
                or prompt(ui, 'Subject: ', rest=prefix, default=''))
        if not subj:
            return None  # skip intro if the user doesn't bother

        subj = prefix + ' ' + subj

        body = ''
        if opts.get('diffstat'):
            # generate a cumulative diffstat of the whole patch series
            diffstat = patch.diffstat(sum(patches, []))
            body = '\n' + diffstat
        else:
            diffstat = None

        body = getdescription(body, sender)
        msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
        msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
        return (msg, subj, diffstat)

    def getbundlemsgs(bundle):
        subj = (opts.get('subject')
                or prompt(ui, 'Subject:', 'A bundle for your repository'))

        body = getdescription('', sender)
        msg = email.MIMEMultipart.MIMEMultipart()
        if body:
            msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
        datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
        datapart.set_payload(bundle)
        bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
        datapart.add_header('Content-Disposition',
                            'attachment',
                            filename=bundlename)
        email.Encoders.encode_base64(datapart)
        msg.attach(datapart)
        msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
        return [(msg, subj, None)]

    sender = (opts.get('from') or ui.config('email', 'from')
              or ui.config('patchbomb', 'from')
              or prompt(ui, 'From', ui.username()))

    if patches:
        msgs = getpatchmsgs(patches, opts.get('patchnames'))
    elif bundle:
        msgs = getbundlemsgs(getbundle(dest))
    else:
        msgs = getpatchmsgs(list(getpatches(revs)))

    showaddrs = []

    def getaddrs(header, ask=False, default=None):
        configkey = header.lower()
        opt = header.replace('-', '_').lower()
        addrs = opts.get(opt)
        if addrs:
            showaddrs.append('%s: %s' % (header, ', '.join(addrs)))
            return mail.addrlistencode(ui, addrs, _charsets, opts.get('test'))

        # not on the command line: fallback to config and then maybe ask
        addr = (ui.config('email', configkey)
                or ui.config('patchbomb', configkey) or '')
        if not addr and ask:
            addr = prompt(ui, header, default=default)
        if addr:
            showaddrs.append('%s: %s' % (header, addr))
            return mail.addrlistencode(ui, [addr], _charsets, opts.get('test'))
        else:
            return default

    to = getaddrs('To', ask=True)
    if not to:
        # we can get here in non-interactive mode
        raise util.Abort(_('no recipient addresses provided'))
    cc = getaddrs('Cc', ask=True, default='') or []
    bcc = getaddrs('Bcc') or []
    replyto = getaddrs('Reply-To')

    if opts.get('diffstat') or opts.get('confirm'):
        ui.write(_('\nFinal summary:\n\n'))
        ui.write('From: %s\n' % sender)
        for addr in showaddrs:
            ui.write('%s\n' % addr)
        for m, subj, ds in msgs:
            ui.write('Subject: %s\n' % subj)
            if ds:
                ui.write(ds)
        ui.write('\n')
        if ui.promptchoice(_('are you sure you want to send (yn)?'),
                           (_('&Yes'), _('&No'))):
            raise util.Abort(_('patchbomb canceled'))

    ui.write('\n')

    parent = opts.get('in_reply_to') or None
    # angle brackets may be omitted, they're not semantically part of the msg-id
    if parent is not None:
        if not parent.startswith('<'):
            parent = '<' + parent
        if not parent.endswith('>'):
            parent += '>'

    first = True

    sender_addr = email.Utils.parseaddr(sender)[1]
    sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
    sendmail = None
    for i, (m, subj, ds) in enumerate(msgs):
        try:
            m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
        except TypeError:
            m['Message-Id'] = genmsgid('patchbomb')
        if parent:
            m['In-Reply-To'] = parent
            m['References'] = parent
        if first:
            parent = m['Message-Id']
            first = False

        m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
        m['Date'] = email.Utils.formatdate(start_time[0], localtime=True)

        start_time = (start_time[0] + 1, start_time[1])
        m['From'] = sender
        m['To'] = ', '.join(to)
        if cc:
            m['Cc'] = ', '.join(cc)
        if bcc:
            m['Bcc'] = ', '.join(bcc)
        if replyto:
            m['Reply-To'] = ', '.join(replyto)
        if opts.get('test'):
            ui.status(_('Displaying '), subj, ' ...\n')
            ui.flush()
            if 'PAGER' in os.environ and not ui.plain():
                fp = util.popen(os.environ['PAGER'], 'w')
            else:
                fp = ui
            generator = email.Generator.Generator(fp, mangle_from_=False)
            try:
                generator.flatten(m, 0)
                fp.write('\n')
            except IOError, inst:
                if inst.errno != errno.EPIPE:
                    raise
            if fp is not ui:
                fp.close()
        else:
            if not sendmail:
                sendmail = mail.connect(ui, mbox=mbox)
            ui.status(_('Sending '), subj, ' ...\n')
            ui.progress(_('sending'), i, item=subj, total=len(msgs))
            if not mbox:
                # Exim does not remove the Bcc field
                del m['Bcc']
            fp = cStringIO.StringIO()
            generator = email.Generator.Generator(fp, mangle_from_=False)
            generator.flatten(m, 0)
            sendmail(sender_addr, to + bcc + cc, fp.getvalue())
Пример #44
0
Displays or modifies local, user, and global configuration.
"""

import re
import os.path
import sys

from mercurial import util, cmdutil
from mercurial.config import config as config_file
from mercurial.i18n import _

sys.path.append(os.path.dirname(__file__))
from deprecate import replace_deprecated, deprecated

if util.version() >= b'4.2':
    from mercurial import rcutil
    rcpath = rcutil.rccomponents
    userrcpath = rcutil.userrcpath
elif util.version() >= b'1.9':
    from mercurial.scmutil import rcpath, userrcpath
else:
    rcpath = util.rcpath
    userrcpath = util.userrcpath

if util.version() >= b'4.7':
    from mercurial.registrar import command
else:
    from mercurial.cmdutil import command