예제 #1
0
def associate(committish, quiet=False):
    """Associate the current branch with a commit-ish.

    :param str or unicode committish: the commit-ish to associate the current branch with
    :param bool quiet: suppress non-error output
    """

    if not directories.is_git_repository():
        messages.error('{0!r} not a git repository'.format(os.getcwd()))
    elif git.is_empty_repository():
        messages.error('cannot associate while empty')
    elif git.is_detached():
        messages.error('cannot associate while HEAD is detached')

    # is it a ref?
    if git.is_ref(committish):
        if not git.is_ref_ambiguous(committish, limit=('heads', 'tags')):
            committish = git.symbolic_full_name(committish)
        else:
            _ambiguous_ref(committish)
    else:
        resolved_committish = git.resolve_sha1(committish)
        if not resolved_committish:
            messages.error('{} is not a valid revision'.format(committish))
        committish = resolved_committish

    current_branch = git.current_branch()
    subprocess.call(['git', 'config', '--local', 'git-changes.associations.' + current_branch + '.with', committish])
    messages.info('{} has been associated with {}'.format(current_branch, committish), quiet)
예제 #2
0
def unassociate(branch=None, cleanup=None, quiet=False, dry_run=False):
    """Unassociate a branch.

    :param str or unicode branch: branch to unassociate
    :param str or unicode cleanup: cleanup action (one of: all, prune)
    :param bool quiet: suppress non-error output
    :param bool dry_run: show the association(s) that would be remove but do nothing
    """

    assert not cleanup or cleanup in ('all', 'prune'), 'cleanup must be one of ' + str(['all', 'prune'])

    if not directories.is_git_repository():
        messages.error('{0!r} not a git repository'.format(os.getcwd()))
    elif git.is_empty_repository():
        return

    if cleanup:
        _prune_associations(cleanup, quiet, dry_run)
    else:
        branch = branch if branch else git.current_branch()
        current_association = get_association(branch)
        if current_association:
            if dry_run:
                messages.info('Would unassociate {0!r} from {1!r}'.format(branch, current_association))
            else:
                subprocess.call(['git', 'config', '--local', '--remove-section', 'git-changes.associations.' + branch])
예제 #3
0
def changes(committish, details=None, color_when=None):
    """Print the changes between a given branch and HEAD.

    :param str or unicode committish: commit-ish to view changes from
    :param str or unicode details: the level of details to show (diff, stat, or None)
    :param str or unicode color_when: when to color output
    """

    assert not details or details in _DETAIL_OPTIONS, 'details must be one of ' + str(_DETAIL_OPTIONS)
    assert not color_when or color_when in _COLOR_OPTIONS, 'color_when must be one of ' + str(_COLOR_OPTIONS)

    if not directories.is_git_repository():
        messages.error('{0!r} not a git repository'.format(os.getcwd()))
    elif not git.is_commit(committish):
        messages.error('{0!r} is not a valid commit'.format(committish))
    elif git.is_ref(committish) and git.is_ref_ambiguous(committish, limit=('heads', 'tags')):
        _ambiguous_ref(committish)

    color_when = git.resolve_coloring(color_when)
    if details == 'diff':
        subprocess.call(['git', 'diff', '--color={}'.format(color_when), committish + '...HEAD'])
    elif details == 'stat':
        subprocess.call(['git', 'diff', '--color={}'.format(color_when), '--stat', committish + '...HEAD'])
    else:
        command = ['git', 'log', '--no-decorate', '--oneline', '{}..HEAD'.format(committish)]
        if details == 'count':
            log = subprocess.check_output(command)
            log = log.splitlines()
            messages.info(str(len(log)))
        else:
            command += ['--color={}'.format(color_when)]
            subprocess.call(command)
예제 #4
0
def _prune_associations(cleanup, quiet, dry_run=False):
    """Remove associations for branches that no longer exist."""

    # get branches and associations
    current_branches = [ref.split()[1][11:] for ref in subprocess.check_output(('git', 'show-ref', '--heads')).splitlines()]
    current_associations = _get_associated_branches()

    branches_to_prune = current_associations
    if cleanup == 'prune':
        # remove only stale associations
        branches_to_prune = list(set(current_associations) - set(current_branches))
    for to_prune in branches_to_prune:
        if dry_run:
            messages.info('Would remove association {0!r}'.format(to_prune), quiet)
        else:
            unassociate(to_prune)
            messages.info('Removed association {0!r}'.format(to_prune), quiet)
예제 #5
0
def restash(stash='stash@{0}', quiet=False):
    """Restash a stash reference.

    :param str or unicode stash: stash reference to reverse apply
    :param bool quiet: suppress all output
    """

    if not subprocess.check_output('git stash list'.split()):
        messages.error('no stashes exist')
    if not _is_valid_stash(stash):
        messages.error('{} is not a valid stash reference'.format(stash))

    _reverse_modifications(stash)
    _remove_untracked_files(stash)

    stash_sha = subprocess.check_output(['git', 'rev-parse', stash]).splitlines()[0]
    messages.info('Restashed {} ({})'.format(stash, stash_sha), quiet)
예제 #6
0
def snapshot(message=None, quiet=False, files=None):
    """Create a snapshot of the working directory and index.

    :param str or unicode message: the message to use when creating the underlying stash
    :param bool quiet: suppress all output
    :param list files: a list of pathspecs to specific files to use when creating the snapshot
    """

    status_command = ['git', 'status', '--porcelain']
    status_output = subprocess.check_output(status_command).splitlines()

    # if there aren't any changes then we don't have anything to do
    if not status_output:
        messages.info('No local changes to save. No snapshot created.', quiet)
        return

    stash_command = ['git', 'stash', 'push', '--include-untracked', '--quiet']
    stash_command = stash_command if message is None else stash_command + ['--message', message]
    stash_command = stash_command if not files else stash_command + ['--'] + files
    _stash_buffer(quiet)
    subprocess.call(stash_command)

    # apply isn't completely quiet when the stash only contains untracked files so swallow all output
    execute.swallow(['git', 'stash', 'apply', '--quiet', '--index'])
예제 #7
0
def makeConfig(name, version, cmdline_args):
    """
    """

    # Read in PCBmodE's configuration file. Look for it in the
    # calling directory, and then where the script is
    msg.info("Processing PCBmodE's configuration file")
    paths = [os.path.join(os.getcwdu()), # project dir
             os.path.join(os.path.dirname(os.path.realpath(__file__)))] # script dir

    filenames = ''
    for path in paths:
        filename = os.path.join(path, cmdline_args.config_file)
        filenames += "  %s \n" % filename
        if os.path.isfile(filename):
            config.cfg = utils.dictFromJsonFile(filename)
            break

    if config.cfg == {}:
        msg.error("Couldn't open PCBmodE's configuration file %s. Looked for it here:\n%s" % (cmdline_args.config_file, filenames))

    # add stuff
    config.cfg['name'] = name
    config.cfg['version'] = version
    config.cfg['base-dir'] = os.path.join(config.cfg['locations']['boards'], name)

    config.cfg['digest-digits'] = 10

    # Read in the board's configuration data
    msg.info("Processing board's configuration file")
    filename = os.path.join(config.cfg['locations']['boards'], 
                            config.cfg['name'], 
                            config.cfg['name'] + '.json')
    config.brd = utils.dictFromJsonFile(filename)

    tmp_dict = config.brd.get('config')
    if tmp_dict != None:
        config.brd['config']['units'] = tmp_dict.get('units', 'mm') or 'mm'
        config.brd['config']['style-layout'] = tmp_dict.get('style-layout', 'default') or 'default'
    else:
        config.brd['config'] = {}
        config.brd['config']['units'] = 'mm'
        config.brd['config']['style-layout'] = 'default'

    # Get style file; search for it in the project directory and 
    # where the script it
    layout_style = config.brd['config']['style-layout']
    layout_style_filename = 'layout.json'
    paths = [os.path.join(config.cfg['base-dir']), # project dir
             os.path.join(os.path.dirname(os.path.realpath(__file__)))] # script dir

    filenames = ''
    for path in paths:
        filename = os.path.join(path, config.cfg['locations']['styles'],
                                layout_style, 
                                layout_style_filename)
        filenames += "  %s \n" % filename
        if os.path.isfile(filename):
            config.stl['layout'] = utils.dictFromJsonFile(filename)
            break

    if config.stl['layout'] == {}:
        msg.error("Couldn't find style file %s. Looked for it here:\n%s" % (layout_style_filename, filenames))

    #=================================
    # Path database
    #=================================
    filename = os.path.join(config.cfg['locations']['boards'], 
                            config.cfg['name'],
                            config.cfg['locations']['build'],
                            'paths_db.json')

    # Open database file. If it doesn't exist, leave the database in
    # ots initial state of {}
    if os.path.isfile(filename):
        config.pth = utils.dictFromJsonFile(filename)


    #=================================
    # Routing
    #=================================
    filename = os.path.join(config.cfg['base-dir'], 
                            config.brd['files'].get('routing-json') or config.cfg['name'] + '_routing.json')

    # Open database file. If it doesn't exist, leave the database in
    # ots initial state of {}
    if os.path.isfile(filename):
        config.rte = utils.dictFromJsonFile(filename)
    else:
        config.rte = {}


    # namespace URLs
    config.cfg['ns'] = {
        None       : "http://www.w3.org/2000/svg",
        "dc"       : "http://purl.org/dc/elements/1.1/",
        "cc"       : "http://creativecommons.org/ns#",
        "rdf"      : "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
        "svg"      : "http://www.w3.org/2000/svg",
        "sodipodi" : "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
        "inkscape" : "http://www.inkscape.org/namespaces/inkscape",
        # Namespace URI are strings; they don't need to be URLs. See:
        #  http://en.wikipedia.org/wiki/XML_namespace
        "pcbmode"  : "pcbmode"
    }

    config.cfg['namespace'] = config.cfg['ns']

    # significant digits to use for floats
    config.cfg['significant-digits'] = config.cfg.get('significant-digits', 8)

    # buffer from board outline to display block edge 
    config.cfg['display-frame-buffer'] = config.cfg.get('display_frame_buffer', 1.0)

    # the style for masks used for copper pours
    config.cfg['mask-style'] = "fill:#000;stroke:#000;stroke-linejoin:round;stroke-width:%s;"

    # Sort out distances
    distances = {
      "from-pour-to": {
        "outline": 0.5,
        "drill": 0.3, 
        "pad": 0.2, 
        "route": 0.25
       }
    }

    config.brd['distances'] = (config.brd.get('distances') or 
                               distances)
    config.brd['distances']['from-pour-to'] = (config.brd['distances'].get('from-pour-to') or
                                               distances['from-pour-to'])
    dcfg = config.brd['distances']['from-pour-to']
    for key in distances['from-pour-to'].keys():
        dcfg[key] = (dcfg[key] or distances[key])

    # Commandline overrides. These are stored in a temporary dictionary
    # so that they are not written to the config file when the board's
    # configuration is dumped, with extraction, for example
    config.tmp = {}
    config.tmp['no-layer-index'] = (cmdline_args.no_layer_index or
                                    config.brd['config'].get('no-layer-index') or
                                    False)
    config.tmp['no-flashes'] = (cmdline_args.no_flashes or
                                config.brd['config'].get('no-flashes') or
                                False)
    config.tmp['no-docs'] = (cmdline_args.no_docs or
                             config.brd['config'].get('no-docs') or
                             False)
    config.tmp['no-drill-index'] = (cmdline_args.no_drill_index or
                                    config.brd['config'].get('no-drill-index') or
                                    False)


    # Define Gerber setting from board's config or defaults
    try:
        tmp = config.brd['gerber']
    except:
        config.brd['gerber'] = {}
    gd = config.brd['gerber']    
    gd['decimals'] = config.brd['gerber'].get('decimals') or 6
    gd['digits'] = config.brd['gerber'].get('digits') or 6
    gd['steps-per-segment'] = config.brd['gerber'].get('steps-per-segment') or 100
    gd['min-segment-length'] = config.brd['gerber'].get('min-segment-length') or 0.05

    # Inkscape inverts the 'y' axis for some historical reasons.
    # This means that we need to invert it as well. This should
    # be the only place this inversion happens so it's easy to
    # control if things change.
    config.cfg['invert-y'] = -1

    # Applying a scale factor to a rectanle can look bad if the height
    # and width are different. For paths, since they are typically
    # irregular, we apply a scale, but for rectangles and circles we
    # apply a buffer

    # Soldemask scales and buffers
    soldermask_dict = {
      "path-scale": 1.05,
      "rect-buffer": 0.05,
      "circle-buffer": 0.05
    }
    config.brd['soldermask'] = config.brd.get('soldermask') or {}
    for key in soldermask_dict:
        value = config.brd['soldermask'].get(key)
        if value == None:
            config.brd['soldermask'][key] = soldermask_dict[key]

    # Solderpaste scale
    solderpaste_dict = {
      "path-scale": 0.9,
      "rect-buffer": -0.1,
      "circle-buffer": -0.1
    }
    config.brd['solderpaste'] = config.brd.get('solderpaste') or {}
    for key in solderpaste_dict:
        value = config.brd['solderpaste'].get(key)
        if value == None:
            config.brd['solderpaste'][key] = solderpaste_dict[key]


    return
예제 #8
0
def main():

    # get PCBmodE version
    version = utils.get_git_revision()

    # setup and parse commandline arguments
    argp = cmdArgSetup(version)
    cmdline_args = argp.parse_args()

    # Might support running multiple boards in the future,
    # for now get the first onw
    board_name = cmdline_args.boards[0]
    makeConfig(board_name, version, cmdline_args)

    # check if build directory exists; if not, create
    build_dir = os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'])
    utils.create_dir(build_dir)

    # renumber refdefs and dump board config file
    if cmdline_args.renumber is not False:
        msg.info("Renumbering refdefs")
        if cmdline_args.renumber is None:
            order = 'top-to-bottom'
        else:
            order = cmdline_args.renumber.lower()    

        utils.renumberRefdefs(order)

    # Extract routing from input SVG file
    elif cmdline_args.extract is True:
        extract.extract()

    # Create a BoM
    elif cmdline_args.make_bom is not False:
        bom.make_bom(cmdline_args.make_bom)

    else:
        # make the board
        if cmdline_args.make is True:
            msg.info("Creating board")
            board = Board()

        # Create production files (Gerbers, Excellon, etc.)
        if cmdline_args.fab is not False:
            if cmdline_args.fab is None:
                manufacturer = 'default'
            else:
                manufacturer = cmdline_args.fab.lower()
     
            msg.info("Creating Gerbers")
            gerber.gerberise(manufacturer)

            msg.info("Creating excellon drill file")
            excellon.makeExcellon(manufacturer)
     
        if cmdline_args.pngs is True:
            msg.info("Creating PNGs")
            utils.makePngs()
   
    
    filename = os.path.join(config.cfg['locations']['boards'], 
                            config.cfg['name'],
                            config.cfg['locations']['build'],
                            'paths_db.json')

    try:
        f = open(filename, 'wb')
    except IOError as e:
        print "I/O error({0}): {1}".format(e.errno, e.strerror)
 
    f.write(json.dumps(config.pth, sort_keys=True, indent=2))
    f.close()

    msg.info("Done!")
예제 #9
0
def _run(start, end, quiet):
    start_stash = 'stash@{{{}}}'.format(start)
    for i in range(start, end):
        stash_sha = subprocess.check_output(['git', 'rev-parse', start_stash]).splitlines()[0]
        subprocess.call(['git', 'stash', 'drop', '--quiet', start_stash])
        messages.info('Dropped refs/stash@{{{}}} ({})'.format(i, stash_sha), quiet)
예제 #10
0
def _dry_run(start, end):
    for i in range(start, end):
        stash = 'stash@{{{}}}'.format(i)
        stash_sha = subprocess.check_output(['git', 'rev-parse', stash]).splitlines()[0]
        messages.info('Would drop refs/{} ({})'.format(stash, stash_sha))
예제 #11
0
def makeConfig(name, version, cmdline_args):
    """
    """

    # Read in PCBmodE's configuration file. Look for it in the
    # calling directory, and then where the script is
    msg.info("Processing PCBmodE's configuration file")
    paths = [os.path.join(os.getcwdu()), # project dir
             os.path.join(os.path.dirname(os.path.realpath(__file__)))] # script dir

    filenames = ''
    for path in paths:
        filename = os.path.join(path, cmdline_args.config_file)
        filenames += "  %s \n" % filename
        if os.path.isfile(filename):
            config.cfg = utils.dictFromJsonFile(filename)
            break

    if config.cfg == {}:
        msg.error("Couldn't open PCBmodE's configuration file %s. Looked for it here:\n%s" % (cmdline_args.config_file, filenames))

    # add stuff
    config.cfg['name'] = name
    config.cfg['version'] = version
    config.cfg['base-dir'] = os.path.join(config.cfg['locations']['boards'], name)

    config.cfg['digest-digits'] = 10

    # Read in the board's configuration data
    msg.info("Processing board's configuration file")
    filename = os.path.join(config.cfg['locations']['boards'], 
                            config.cfg['name'], 
                            config.cfg['name'] + '.json')
    config.brd = utils.dictFromJsonFile(filename)

    tmp_dict = config.brd.get('config')
    if tmp_dict != None:
        config.brd['config']['units'] = tmp_dict.get('units', 'mm') or 'mm'
        config.brd['config']['style-layout'] = tmp_dict.get('style-layout', 'default') or 'default'
    else:
        config.brd['config'] = {}
        config.brd['config']['units'] = 'mm'
        config.brd['config']['style-layout'] = 'default'


    #=================================
    # Style
    #=================================

    # Get style file; search for it in the project directory and 
    # where the script it
    layout_style = config.brd['config']['style-layout']
    layout_style_filename = 'layout.json'
    paths = [os.path.join(config.cfg['base-dir']), # project dir
             os.path.join(os.path.dirname(os.path.realpath(__file__)))] # script dir

    filenames = ''
    for path in paths:
        filename = os.path.join(path, config.cfg['locations']['styles'],
                                layout_style, 
                                layout_style_filename)
        filenames += "  %s \n" % filename
        if os.path.isfile(filename):
            config.stl['layout'] = utils.dictFromJsonFile(filename)
            break

    if config.stl['layout'] == {}:
        msg.error("Couldn't find style file %s. Looked for it here:\n%s" % (layout_style_filename, filenames))

    #-------------------------------------------------------------
    # Stackup
    #-------------------------------------------------------------
    try:
        stackup_filename = config.brd['stackup']['name'] + '.json'
    except:
        stackup_filename = 'two-layer.json'

    paths = [os.path.join(config.cfg['base-dir']), # project dir
             os.path.join(os.path.dirname(os.path.realpath(__file__)))] # script dir

    filenames = ''
    for path in paths:
        filename = os.path.join(path, config.cfg['locations']['stackups'],
                                stackup_filename)
        filenames += "  %s \n" % filename
        if os.path.isfile(filename):
            config.stk = utils.dictFromJsonFile(filename)
            break

    if config.stk == {}:
        msg.error("Couldn't find stackup file %s. Looked for it here:\n%s" % (stackup_filename, filenames))

    config.stk['layers-dict'], config.stk['layer-names'] = utils.getLayerList()
    config.stk['surface-layers'] = [config.stk['layers-dict'][0], config.stk['layers-dict'][-1]]
    config.stk['internal-layers'] = config.stk['layers-dict'][1:-1]
    config.stk['surface-layer-names'] = [config.stk['layer-names'][0], config.stk['layer-names'][-1]]
    config.stk['internal-layer-names'] = config.stk['layer-names'][1:-1]

    #---------------------------------------------------------------
    # Path database
    #---------------------------------------------------------------
    filename = os.path.join(config.cfg['locations']['boards'], 
                            config.cfg['name'],
                            config.cfg['locations']['build'],
                            'paths_db.json')

    # Open database file. If it doesn't exist, leave the database in
    # ots initial state of {}
    if os.path.isfile(filename):
        config.pth = utils.dictFromJsonFile(filename)


    #----------------------------------------------------------------
    # Routing
    #----------------------------------------------------------------
    filename = os.path.join(config.cfg['base-dir'], 
                            config.brd['files'].get('routing-json') or config.cfg['name'] + '_routing.json')

    # Open database file. If it doesn't exist, leave the database in
    # ots initial state of {}
    if os.path.isfile(filename):
        config.rte = utils.dictFromJsonFile(filename)
    else:
        config.rte = {}


    # namespace URLs
    config.cfg['ns'] = {
        None       : "http://www.w3.org/2000/svg",
        "dc"       : "http://purl.org/dc/elements/1.1/",
        "cc"       : "http://creativecommons.org/ns#",
        "rdf"      : "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
        "svg"      : "http://www.w3.org/2000/svg",
        "sodipodi" : "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
        "inkscape" : "http://www.inkscape.org/namespaces/inkscape",
        # Namespace URI are strings; they don't need to be URLs. See:
        #  http://en.wikipedia.org/wiki/XML_namespace
        "pcbmode"  : "pcbmode"
    }

    config.cfg['namespace'] = config.cfg['ns']

    # significant digits to use for floats
    config.cfg['significant-digits'] = config.cfg.get('significant-digits', 8)

    # buffer from board outline to display block edge 
    config.cfg['display-frame-buffer'] = config.cfg.get('display_frame_buffer', 1.0)

    # the style for masks used for copper pours
    config.cfg['mask-style'] = "fill:#000;stroke:#000;stroke-linejoin:round;stroke-width:%s;"


    #------------------------------------------------------------------
    # Distances
    #------------------------------------------------------------------
    # If any of the distance definitions are missing from the board's
    # configuration file, use PCBmodE's defaults
    #------------------------------------------------------------------
    config_distances_dict = config.cfg['distances']
    try:
        board_distances_dict = config.brd.get('distances')
    except:
        board_distances_dict = {}

    distance_keys = ['from-pour-to', 'soldermask', 'solderpaste']

    for dk in distance_keys:
        config_dict = config_distances_dict[dk]
        try:
            board_dict = board_distances_dict[dk]
        except:
            board_distances_dict[dk] = {}
            board_dict = board_distances_dict[dk]
        
        for k in config_dict.keys():
            board_dict[k] = (board_dict.get(k) or config_dict[k])

    #-----------------------------------------------------------------
    # Commandline overrides
    #-----------------------------------------------------------------
    # These are stored in a temporary dictionary so that they are not
    # written to the config file when the board's configuration is
    # dumped, with extraction, for example
    #-----------------------------------------------------------------
    config.tmp = {}
    config.tmp['no-layer-index'] = (cmdline_args.no_layer_index or
                                    config.brd['config'].get('no-layer-index') or
                                    False)
    config.tmp['no-flashes'] = (cmdline_args.no_flashes or
                                config.brd['config'].get('no-flashes') or
                                False)
    config.tmp['no-docs'] = (cmdline_args.no_docs or
                             config.brd['config'].get('no-docs') or
                             False)
    config.tmp['no-drill-index'] = (cmdline_args.no_drill_index or
                                    config.brd['config'].get('no-drill-index') or
                                    False)


    # Define Gerber setting from board's config or defaults
    try:
        tmp = config.brd['gerber']
    except:
        config.brd['gerber'] = {}
    gd = config.brd['gerber']    
    gd['decimals'] = config.brd['gerber'].get('decimals') or 6
    gd['digits'] = config.brd['gerber'].get('digits') or 6
    gd['steps-per-segment'] = config.brd['gerber'].get('steps-per-segment') or 100
    gd['min-segment-length'] = config.brd['gerber'].get('min-segment-length') or 0.05

    # Inkscape inverts the 'y' axis for some historical reasons.
    # This means that we need to invert it as well. This should
    # be the only place this inversion happens so it's easy to
    # control if things change.
    config.cfg['invert-y'] = -1


    return
예제 #12
0
def state(**kwargs):
    """Print the state of the working tree.

    :keyword str show_color: color when (always, never, or auto)
    :keyword str format: format for output (compact or pretty)
    :keyword bool show_status: show status
    :keyword list ignore_extensions: extensions to hide even if the configuration is to show
    :keyword list show_extensions: extensions to show even if the configuration is to hide
    :keyword dict options: dictionary of extension to option list
    :keyword bool show_empty: show empty sections
    :keyword list order: order to print sections in
    :keyword bool clear: clear terminal before printing
    :keyword bool page: page output if too long
    """

    if not directories.is_git_repository():
        messages.error('{0!r} not a git repository'.format(os.getcwd()))

    show_color = git.resolve_coloring(kwargs.get('show_color').lower())
    colorama.init(strip=(show_color == 'never'))

    kwargs['show_color'] = show_color
    kwargs['show_clean_message'] = settings.get(
        'git-state.status.show-clean-message',
        default=True,
        as_type=parse_string.as_bool
    )

    format_ = kwargs.get('format_')
    sections = OrderedDict()
    if git.is_empty_repository():
        if kwargs.get('show_status'):
            status_output = status.get(new_repository=True, **kwargs)
            status_title = status.title()
            status_accent = status.accent(new_repository=True, **kwargs)
            sections[status_title] = _print_section(status_title, status_accent, status_output, format_, color=show_color)
    else:
        if kwargs.get('show_status'):
            status_output = status.get(**kwargs)
            status_title = status.title()
            status_accent = status.accent(show_color=show_color)
            # TODO: remove restriction that status is always shown
            sections[status_title] = _print_section(status_title, status_accent, status_output, format_, show_empty=True, color=show_color)

        # show any user defined sections
        extensions = settings.list_(
            section='git-state.extensions',
            config=None,
            count=False,
            keys=True,
            format_=None,
            file_=None
        ).splitlines()
        extensions = list(set(extensions) - set(kwargs.get('ignore_extensions')))
        show_extensions = kwargs.get('show_extensions', [])
        options = kwargs.get('options')
        for extension in extensions or []:

            # skip if we should ignore this extension
            if extension not in show_extensions and not settings.get('git-state.extensions.' + extension + '.show', default=True, as_type=parse_string.as_bool):
                continue

            extension_command = settings.get('git-state.extensions.' + extension)
            extension_name = settings.get('git-state.extensions.' + extension + '.name', default=extension)

            # merge config and command line options
            extension_options = settings.get(
                'git-state.extensions.' + extension + '.options',
                default=[],
                as_type=(lambda value: [value])  # pragma: no cover since this call is mocked and the lambda never fires
            )
            extension_options += options[extension_name] if extension_name in options else []
            extension_options = [o for sub in [shlex.split(line) for line in extension_options] for o in sub]

            extension_command = shlex.split(extension_command) + extension_options
            if settings.get('git-state.extensions.' + extension + '.color', default=True, as_type=parse_string.as_bool):
                extension_command += ['--color={}'.format(show_color)]

            extension_proc = subprocess.Popen(extension_command, stdout=PIPE, stderr=PIPE)
            extension_out, extension_error = extension_proc.communicate()

            sections[extension_name] = _print_section(
                title=extension_name,
                text=extension_out if not extension_proc.returncode else extension_error,
                format_=format_,
                show_empty=kwargs.get('show_empty'),
                color=show_color
            )

    state_result = ''

    # print sections with a predefined order
    order = kwargs.get('order', settings.get('git-state.order', default=[], as_type=parse_string.as_delimited_list('|')))
    for section in order:
        if section in sections:
            state_result += sections.pop(section)

    # print any remaining sections in the order they were defined
    for section_info in sections:
        state_result += sections[section_info]

    if state_result:
        state_result = state_result[:-1]  # strip the extra trailing newline
        state_lines = len(state_result.splitlines())
        terminal_lines = literal_eval(subprocess.check_output(['tput', 'lines']))
        if not kwargs.get('page', True) or terminal_lines >= state_lines + 2:  # one for the newline and one for the prompt
            if kwargs.get('clear') and sys.stdout.isatty():
                subprocess.call('clear')
            messages.info(state_result)
        else:
            echo = subprocess.Popen(['echo', state_result], stdout=PIPE)
            subprocess.call(['less', '-r'], stdin=echo.stdout)
            echo.wait()