legit-1.2.0.post0/0000755000076500000240000000000013633166313014112 5ustar fmingstaff00000000000000legit-1.2.0.post0/PKG-INFO0000644000076500000240000001206113633166313015207 0ustar fmingstaff00000000000000Metadata-Version: 1.2 Name: legit Version: 1.2.0.post0 Summary: Git for Humans. Home-page: https://github.com/frostming/legit Author: Kenneth Reitz Author-email: me@kennethreitz.com License: BSD Description: .. -*-restructuredtext-*- Legit: Git for Humans ===================== Inspired by GitHub for Mac. The Concept ----------- `GitHub for Mac `_ is not just a Git client. This `comment `_ on Hacker News says it best: They haven't re-created the git CLI tool in a GUI, they've created something different. They've created a tool that makes Git more accessible. Little things like auto-stashing when you switch branches will confuse git veterans, but it will make Git much easier to grok for newcomers because of the assumptions it makes about your Git workflow. Why not bring this innovation back to the command line? The Interface ------------- ``switch `` Switches to specified branch. Defaults to current branch. Automatically stashes and unstashes any changes. (alias: ``sw``) ``sync []`` Synchronizes the given branch. Defaults to current branch. Stash, Fetch, Auto-Merge/Rebase, Push, and Unstash. You can only sync published branches. (alias: ``sy``) ``publish []`` Publishes specified branch to the remote. (alias: ``pub``) ``unpublish `` Removes specified branch from the remote. (alias: ``unp``) ``undo`` Un-does the last commit in git history. (alias: ``un``) ``branches []`` Display a list of available branches. Allows wildcard pattern matching of branch name. The Installation ---------------- .. image:: https://img.shields.io/pypi/v/legit.svg :target: https://pypi.python.org/pypi/legit/ .. image:: https://img.shields.io/travis/frostming/legit/master.svg :target: https://travis-ci.org/frostming/legit/ .. image:: https://img.shields.io/coveralls/github/frostming/legit.svg :target: https://coveralls.io/r/frostming/legit/ .. image:: https://repl.it/badge/github/frostming/legit :target: https://repl.it/github/frostming/legit From `PyPI `_ with the Python package manager:: pip install legit Or download a standalone Windows executable from `GitHub Releases `_. To install the cutting edge version from the git repository:: git clone https://github.com/frostming/legit.git cd legit python setup.py install Note: if you encountered `Permission denied`, prepend `sudo` before the `pip` or `python setup.py` command. You'll then have the wonderful ``legit`` command available. Run it within a repository. To view usage and examples, run ``legit`` with no commands or options:: legit To install the git aliases, run the following command:: legit --install To uninstall the git aliases, run the following command:: legit --uninstall Command Options --------------- All legit commands support ``--verbose`` and ``--fake`` options. In order to view the git commands invoked by legit, use the ``--verbose`` option:: legit sync --verbose If you want to see the git commands used by legit but don't want them invoked, use the ``--fake`` option:: legit publish --fake Caveats ------- - All remote operations are carried out by the remote identified in ``$ git config legit.remote remotename`` - If a ``stash pop`` merge fails, Legit stops. I'd like to add checking for a failed merge, and undo the command with friendly error reporting. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.* legit-1.2.0.post0/LICENSE0000644000076500000240000000302113552057442015115 0ustar fmingstaff00000000000000Copyright (c) 2013, Kenneth Reitz All rights reserved. Copyright (c) 2019, Frost Ming All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (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. legit-1.2.0.post0/MANIFEST.in0000644000076500000240000000007613514335525015654 0ustar fmingstaff00000000000000include reqs.txt README.rst LICENSE recursive-include extra * legit-1.2.0.post0/legit.egg-info/0000755000076500000240000000000013633166313016710 5ustar fmingstaff00000000000000legit-1.2.0.post0/legit.egg-info/PKG-INFO0000644000076500000240000001206113633166313020005 0ustar fmingstaff00000000000000Metadata-Version: 1.2 Name: legit Version: 1.2.0.post0 Summary: Git for Humans. Home-page: https://github.com/frostming/legit Author: Kenneth Reitz Author-email: me@kennethreitz.com License: BSD Description: .. -*-restructuredtext-*- Legit: Git for Humans ===================== Inspired by GitHub for Mac. The Concept ----------- `GitHub for Mac `_ is not just a Git client. This `comment `_ on Hacker News says it best: They haven't re-created the git CLI tool in a GUI, they've created something different. They've created a tool that makes Git more accessible. Little things like auto-stashing when you switch branches will confuse git veterans, but it will make Git much easier to grok for newcomers because of the assumptions it makes about your Git workflow. Why not bring this innovation back to the command line? The Interface ------------- ``switch `` Switches to specified branch. Defaults to current branch. Automatically stashes and unstashes any changes. (alias: ``sw``) ``sync []`` Synchronizes the given branch. Defaults to current branch. Stash, Fetch, Auto-Merge/Rebase, Push, and Unstash. You can only sync published branches. (alias: ``sy``) ``publish []`` Publishes specified branch to the remote. (alias: ``pub``) ``unpublish `` Removes specified branch from the remote. (alias: ``unp``) ``undo`` Un-does the last commit in git history. (alias: ``un``) ``branches []`` Display a list of available branches. Allows wildcard pattern matching of branch name. The Installation ---------------- .. image:: https://img.shields.io/pypi/v/legit.svg :target: https://pypi.python.org/pypi/legit/ .. image:: https://img.shields.io/travis/frostming/legit/master.svg :target: https://travis-ci.org/frostming/legit/ .. image:: https://img.shields.io/coveralls/github/frostming/legit.svg :target: https://coveralls.io/r/frostming/legit/ .. image:: https://repl.it/badge/github/frostming/legit :target: https://repl.it/github/frostming/legit From `PyPI `_ with the Python package manager:: pip install legit Or download a standalone Windows executable from `GitHub Releases `_. To install the cutting edge version from the git repository:: git clone https://github.com/frostming/legit.git cd legit python setup.py install Note: if you encountered `Permission denied`, prepend `sudo` before the `pip` or `python setup.py` command. You'll then have the wonderful ``legit`` command available. Run it within a repository. To view usage and examples, run ``legit`` with no commands or options:: legit To install the git aliases, run the following command:: legit --install To uninstall the git aliases, run the following command:: legit --uninstall Command Options --------------- All legit commands support ``--verbose`` and ``--fake`` options. In order to view the git commands invoked by legit, use the ``--verbose`` option:: legit sync --verbose If you want to see the git commands used by legit but don't want them invoked, use the ``--fake`` option:: legit publish --fake Caveats ------- - All remote operations are carried out by the remote identified in ``$ git config legit.remote remotename`` - If a ``stash pop`` merge fails, Legit stops. I'd like to add checking for a failed merge, and undo the command with friendly error reporting. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.* legit-1.2.0.post0/legit.egg-info/SOURCES.txt0000644000076500000240000000063113633166313020574 0ustar fmingstaff00000000000000LICENSE MANIFEST.in README.rst setup.cfg setup.py extra/bash-completion/legit extra/man/legit.1 extra/zsh-completion/_legit legit/__init__.py legit/bootstrap.py legit/cli.py legit/core.py legit/scm.py legit/settings.py legit/utils.py legit.egg-info/PKG-INFO legit.egg-info/SOURCES.txt legit.egg-info/dependency_links.txt legit.egg-info/entry_points.txt legit.egg-info/requires.txt legit.egg-info/top_level.txtlegit-1.2.0.post0/legit.egg-info/entry_points.txt0000644000076500000240000000005113633166313022202 0ustar fmingstaff00000000000000[console_scripts] legit = legit.cli:cli legit-1.2.0.post0/legit.egg-info/requires.txt0000644000076500000240000000004213633166313021304 0ustar fmingstaff00000000000000click clint crayons GitPython six legit-1.2.0.post0/legit.egg-info/top_level.txt0000644000076500000240000000000613633166313021436 0ustar fmingstaff00000000000000legit legit-1.2.0.post0/legit.egg-info/dependency_links.txt0000644000076500000240000000000113633166313022756 0ustar fmingstaff00000000000000 legit-1.2.0.post0/setup.py0000644000076500000240000000530513631173762015633 0ustar fmingstaff00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import os import sys import re from codecs import open # To use a consistent encoding from shutil import rmtree from setuptools import setup, Command # Always prefer setuptools over distutils APP_NAME = "legit" with open("legit/core.py") as f: VERSION = re.findall(r'^__version__ *= *[\'"](.+?)[\'"]', f.read(), flags=re.M)[0] settings = dict() class UploadCommand(Command): """Support setup.py upload.""" description = 'Build and publish the package.' user_options = [] @staticmethod def status(s): """Prints things in bold.""" print('\033[1m{}\033[0m'.format(s)) def initialize_options(self): pass def finalize_options(self): pass def run(self): try: self.status('Removing previous builds...') rmtree('dist') except OSError: pass self.status('Building Source and Wheel (universal) distribution...') os.system('{} setup.py sdist bdist_wheel --universal'.format(sys.executable)) self.status('Uploading the package to PyPI via Twine...') os.system('twine upload dist/*') self.status('Pushing git tags...') os.system('git tag -a {0} -m "v{0}"'.format(VERSION)) os.system('git push --tags') sys.exit() if sys.argv[-1] == "build_manpage": os.system("rst2man.py README.rst > extra/man/legit.1") sys.exit() # Build Helper. if sys.argv[-1] == "build": os.system("pyinstaller --onefile legit_r") settings.update( name=APP_NAME, version=VERSION, description="Git for Humans.", long_description=open("README.rst").read(), author="Kenneth Reitz", author_email="me@kennethreitz.com", url="https://github.com/frostming/legit", packages=["legit"], install_requires=[ 'click', 'clint', 'crayons', 'GitPython', 'six' ], license="BSD", python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*", classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Natural Language :: English", "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", ], entry_points={"console_scripts": ["legit = legit.cli:cli"]}, cmdclass={"publish": UploadCommand} ) setup(**settings) legit-1.2.0.post0/extra/0000755000076500000240000000000013633166313015235 5ustar fmingstaff00000000000000legit-1.2.0.post0/extra/man/0000755000076500000240000000000013633166313016010 5ustar fmingstaff00000000000000legit-1.2.0.post0/extra/man/legit.10000644000076500000240000000562513552057442017210 0ustar fmingstaff00000000000000.\" Man page generated from reStructuredText. . .TH LEGIT: GIT FOR HUMANS "" "" "" .SH NAME Legit: Git for Humans \- . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .\" -*-restructuredtext-*- . .sp Inspired by GitHub for Mac. .SH THE CONCEPT .sp \fI\%GitHub for Mac\fP is not just a Git client. .sp This \fI\%comment\fP on Hacker News says it best: .INDENT 0.0 .INDENT 3.5 They haven\(aqt re\-created the git CLI tool in a GUI, they\(aqve created something different. They\(aqve created a tool that makes Git more accessible. Little things like auto\-stashing when you switch branches will confuse git veterans, but it will make Git much easier to grok for newcomers because of the assumptions it makes about your Git workflow. .UNINDENT .UNINDENT .sp Why not bring this innovation back to the command line? .SH THE INTERFACE .INDENT 0.0 .TP .B \fBbranches []\fP Get a nice pretty list of available branches. Allows wildcard branch name matching. .TP .B \fBsync []\fP Synchronizes the given branch. Defaults to current branch. Stash, Fetch, Auto\-Merge/Rebase, Push, and Unstash. You can only sync published branches. .TP .B \fBswitch \fP Switches to specified branch. Defaults to current branch. Automatically stashes and unstashes any changes. .TP .B \fBpublish []\fP Publishes specified branch to the remote. .TP .B \fBunpublish \fP Removes specified branch from the remote. .TP .B \fBinstall\fP Installs legit git aliases. .UNINDENT .SH THE INSTALLATION \fI\%[image: https://img.shields.io/pypi/v/legit.svg] \fP .sp From \fI\%PyPI\fP with the Python package manager: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C pip install legit .ft P .fi .UNINDENT .UNINDENT .sp Or download a standalone Windows executable from \fI\%GitHub Releases\fP\&. .sp You\(aqll then have the wonderful \fBlegit\fP command available. Run it within a repository. .sp To install the git aliases, run the following command: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C legit install .ft P .fi .UNINDENT .UNINDENT .SH CAVEATS .INDENT 0.0 .IP \(bu 2 All remote operations are carried out by the remote identified in \fB$ git config legit.remote remotename\fP .IP \(bu 2 If a \fBstash pop\fP merge fails, Legit stops. I\(aqd like to add checking for a failed merge, and undo the command with friendly error reporting. .UNINDENT .\" Generated by docutils manpage writer. . legit-1.2.0.post0/extra/zsh-completion/0000755000076500000240000000000013633166313020210 5ustar fmingstaff00000000000000legit-1.2.0.post0/extra/zsh-completion/_legit0000644000076500000240000000562413552057442021407 0ustar fmingstaff00000000000000#compdef legit # ------------------------------------------------------------------------------ # Description # ----------- # # Completion script for legit (https://github.com/kennethreitz/legit). # # ------------------------------------------------------------------------------ # Authors # ------- # # * Ryan James (https://github.com/autoplectic) # # ------------------------------------------------------------------------------ _legit () { local curcontext="$curcontext" state line typeset -A opt_args _arguments -C \ '1:command:->command' \ '*::options:->options' local -a subcommands subcommands=( 'settings: Display and edit the current Legit settings.' 'branches: Get a nice pretty list of available branches. Allows wildcard branch name matching.' 'sync: Synchronizes the given branch. Defaults to current branch. Stash, Fetch, Auto-Merge/Rebase, Push, and Unstash. You can only sync published branches.' 'switch: Switches to specified branch. Defaults to current branch. Automatically stashes and unstashes any changes.' 'publish: Publishes specified branch to the remote.' 'unpublish: Removes specified branch from the remote.' 'help: Displays help for legit command.' ) case $state in (command) _describe -t commands 'legit' subcommands ;; (options) case $line[1] in (settings|branches) ;; (sy|sync|sw|switch|pub|publish|unp|unpublish|rs) _arguments \ ':branch:__git_branch_names' ;; (help) _describe -t commands 'legit' subcommands ;; esac esac } # Helper functions copied from Completion/Unix/Command/_git (a2098b0) {{{ # The approach to trigger the load of _git completion did not work (recursion, ...): # https://dev.0x50.de/projects/ftzsh/repository/revisions/master/entry/functions/_ta#L17 (( $+functions[__git_branch_names] )) || __git_branch_names () { local expl declare -a branch_names branch_names=(${${(f)"$(_call_program branchrefs git for-each-ref --format='"%(refname)"' refs/heads 2>/dev/null)"}#refs/heads/}) __git_command_successful $pipestatus || return 1 _wanted branch-names expl branch-name compadd $* - $branch_names } (( $+functions[__git_command_successful] )) || __git_command_successful () { if (( ${#*:#0} > 0 )); then _message 'not a git repository' return 1 fi return 0 } # }}} _legit # -*- mode: zsh; -*- # vim: ft=zsh sw=8 ts=8 et legit-1.2.0.post0/extra/bash-completion/0000755000076500000240000000000013633166313020321 5ustar fmingstaff00000000000000legit-1.2.0.post0/extra/bash-completion/legit0000644000076500000240000000353013514521121021337 0ustar fmingstaff00000000000000_legit() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="branches publish switch sync unpublish" local running=`git for-each-ref --format='%(refname:short)' --sort='refname:short' refs/heads` case "${prev}" in sy|sync) COMPREPLY=( $(compgen -W "${running}" -- ${cur}) ) return 0 ;; sw|switch) COMPREPLY=( $(compgen -W "${running}" -- ${cur}) ) return 0 ;; pub|publish) COMPREPLY=( $(compgen -W "${running}" -- ${cur}) ) return 0 ;; unp|unpublish|rs) local running=$(for x in `git ls-remote 2>/dev/null | grep refs/heads | awk '{ print $2 }' | sed -e 's/refs\/heads\///g'`; do echo ${x} ; done ) COMPREPLY=( $(compgen -W "${running}" -- ${cur}) ) return 0 ;; *) ;; esac COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 } complete -F _legit legit _git_unpublish() { local cur="${COMP_WORDS[COMP_CWORD]}" local running=$(for x in `git ls-remote 2>/dev/null | grep refs/heads | awk '{ print $2 }' | sed -e 's/refs\/heads\///g'`; do echo ${x} ; done ) COMPREPLY=( $(compgen -W "${running}" -- ${cur}) ) return 0 } _complete_with_git_branch() { local cur="${COMP_WORDS[COMP_CWORD]}" local running=`git for-each-ref --format='%(refname:short)' --sort='refname:short' refs/heads` COMPREPLY=( $(compgen -W "${running}" -- ${cur}) ) return 0 } function _git_sync { _complete_with_git_branch; } function _git_switch { _complete_with_git_branch; } function _git_publish { _complete_with_git_branch; } complete -F _git_sync git-sync complete -F _git_switch git-switch complete -F _git_publish git-publish complete -F _git_unpublish git-unpublish legit-1.2.0.post0/legit/0000755000076500000240000000000013633166313015216 5ustar fmingstaff00000000000000legit-1.2.0.post0/legit/bootstrap.py0000644000076500000240000000300013514521121017564 0ustar fmingstaff00000000000000# -*- coding: utf-8 -*- """ legit.bootstrap ~~~~~~~~~~~~~~~ This module boostraps the Legit runtime. """ from clint import resources from clint.textui import colored import crayons from six.moves import configparser from .settings import legit_settings resources.init('kennethreitz', 'legit') try: config_file = resources.user.open('config.ini', 'r') except IOError: resources.user.write('config.ini', '') config_file = resources.user.open('config.ini', 'r') # Load existing configuration. config = configparser.ConfigParser() try: # `read_file()` added in Python 3.2 config.read_file(config_file) except AttributeError: config.readfp(config_file) # Populate if needed. if not config.has_section('legit'): config.add_section('legit') modified = False # Set defaults if they are missing. # Add everything to settings object. for (k, v, _) in legit_settings.config_defaults: if not config.has_option('legit', k): modified = True config.set('legit', k, v) setattr(legit_settings, k, v) else: val = config.get('legit', k) # Map boolean strings. if val.lower() in ('true', '1', 'yep', 'sure'): val = True elif val.lower() in ('false', '0', 'nope', 'nadda', 'nah'): val = False setattr(legit_settings, k, val) if modified: config_file = resources.user.open('config.ini', 'w') config.write(config_file) if legit_settings.disable_colors: crayons.disable() colored.DISABLE_COLOR = True legit-1.2.0.post0/legit/__init__.py0000644000076500000240000000006513514521121017316 0ustar fmingstaff00000000000000# -*- coding: utf-8 -*- from .core import * # noqa legit-1.2.0.post0/legit/core.py0000644000076500000240000000034413633166260016522 0ustar fmingstaff00000000000000# -*- coding: utf-8 -*- """ legit.core ~~~~~~~~~~ This module provides the basic functionality of legit. """ from . import bootstrap del bootstrap __version__ = "1.2.0post0" __author__ = "Kenneth Reitz" __license__ = "BSD" legit-1.2.0.post0/legit/cli.py0000644000076500000240000002730313631173762016350 0ustar fmingstaff00000000000000# -*- coding: utf-8 -*- """ legit.cli ~~~~~~~~~ This module provides the CLI interface to legit. """ import os import click import crayons from clint import resources from clint.textui import columns from .core import __version__ from .scm import SCMRepo from .settings import legit_settings from .utils import ( black, format_help, git_version, order_manually, output_aliases, status_log, verbose_echo, program_path ) CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) pass_scm = click.make_pass_decorator(SCMRepo) class LegitGroup(click.Group): """Custom Group class with specially sorted command list""" command_aliases = { 'pub': 'publish', 'sw': 'switch', 'sy': 'sync', 'unp': 'unpublish', 'un': 'undo', } def list_commands(self, ctx): """Override for showing commands in particular order""" commands = super(LegitGroup, self).list_commands(ctx) return [cmd for cmd in order_manually(commands)] def get_command(self, ctx, cmd_name): """Override to handle command aliases""" rv = click.Group.get_command(self, ctx, cmd_name) if rv is not None: return rv cmd_name = self.command_aliases.get(cmd_name, "") return click.Group.get_command(self, ctx, cmd_name) def get_help_option(self, ctx): """Override for showing formatted main help via --help and -h options""" help_options = self.get_help_option_names(ctx) if not help_options or not self.add_help_option: return def show_help(ctx, param, value): if value and not ctx.resilient_parsing: if not ctx.obj: # legit main help click.echo(format_help(ctx.get_help())) else: # legit sub-command help click.echo(ctx.get_help(), color=ctx.color) ctx.exit() return click.Option( help_options, is_flag=True, is_eager=True, expose_value=False, callback=show_help, help='Show this message and exit.') @click.group(cls=LegitGroup, invoke_without_command=True, context_settings=CONTEXT_SETTINGS) @click.version_option(prog_name=black('legit', bold=True), version=__version__) @click.option('--verbose', is_flag=True, help='Enables verbose mode.') @click.option('--fake', is_flag=True, help='Show but do not invoke git commands.') @click.option('--install', is_flag=True, help='Install legit git aliases.') @click.option('--uninstall', is_flag=True, help='Uninstall legit git aliases.') @click.option('--config', is_flag=True, help='Edit legit configuration file.') @click.pass_context def cli(ctx, verbose, fake, install, uninstall, config): """legit command line interface""" # Create a repo object and remember it as as the context object. From # this point onwards other commands can refer to it by using the # @pass_scm decorator. ctx.obj = SCMRepo() ctx.obj.fake = fake ctx.obj.verbose = fake or verbose if install: do_install(ctx, verbose, fake) ctx.exit() elif uninstall: do_uninstall(ctx, verbose, fake) ctx.exit() elif config: do_edit_settings(fake) ctx.exit() else: if ctx.invoked_subcommand is None: # Display help to user if no commands were passed. click.echo(format_help(ctx.get_help())) @cli.command(short_help='Switches to specified branch.') @click.argument('to_branch', required=False) @click.option('--verbose', is_flag=True, help='Enables verbose mode.') @click.option('--fake', is_flag=True, help='Show but do not invoke git commands.') @pass_scm def switch(scm, to_branch, verbose, fake): """Switches from one branch to another, safely stashing and restoring local changes. """ scm.fake = fake scm.verbose = fake or verbose scm.repo_check() if to_branch is None: scm.display_available_branches() raise click.BadArgumentUsage('Please specify a branch to switch to') scm.stash_log() status_log(scm.checkout_branch, 'Switching to {}.'.format( crayons.yellow(to_branch)), to_branch) scm.unstash_log() @cli.command(short_help='Synchronizes the given branch with remote.') @click.argument('to_branch', required=False) @click.option('--verbose', is_flag=True, help='Enables verbose mode.') @click.option('--fake', is_flag=True, help='Show but do not invoke git commands.') @pass_scm @click.pass_context def sync(ctx, scm, to_branch, verbose, fake): """Stashes unstaged changes, Fetches remote data, Performs smart pull+merge, Pushes local commits up, and Unstashes changes. Defaults to current branch. """ scm.fake = fake scm.verbose = fake or verbose scm.repo_check(require_remote=True) if to_branch: # Optional branch specifier. branch = scm.fuzzy_match_branch(to_branch) if branch: is_external = True original_branch = scm.get_current_branch_name() else: raise click.BadArgumentUsage( "Branch {} does not exist. Use an existing branch." .format(crayons.yellow(branch))) else: # Sync current branch. branch = scm.get_current_branch_name() is_external = False if branch in scm.get_branch_names(local=False): if is_external: ctx.invoke(switch, to_branch=branch, verbose=verbose, fake=fake) scm.stash_log(sync=True) status_log(scm.smart_pull, 'Pulling commits from the server.') status_log(scm.push, 'Pushing commits to the server.', branch) scm.unstash_log(sync=True) if is_external: ctx.invoke(switch, to_branch=original_branch, verbose=verbose, fake=fake) else: raise click.BadArgumentUsage( "Branch {} is not published. Publish before syncing." .format(crayons.yellow(branch))) @cli.command(short_help='Publishes specified branch to the remote.') @click.argument('to_branch', required=False) @click.option('--verbose', is_flag=True, help='Enables verbose mode.') @click.option('--fake', is_flag=True, help='Show but do not invoke git commands.') @pass_scm def publish(scm, to_branch, verbose, fake): """Pushes an unpublished branch to a remote repository.""" scm.fake = fake scm.verbose = fake or verbose scm.repo_check(require_remote=True) branch = scm.fuzzy_match_branch(to_branch) if not branch: branch = scm.get_current_branch_name() scm.display_available_branches() if to_branch is None: click.echo("Using current branch {}".format(crayons.yellow(branch))) else: click.echo( "Branch {} not found, using current branch {}" .format(crayons.red(to_branch), crayons.yellow(branch))) branch_names = scm.get_branch_names(local=False) if branch in branch_names: raise click.BadArgumentUsage( "Branch {} is already published. Use a branch that is not published." .format(crayons.yellow(branch))) status_log(scm.publish_branch, 'Publishing {}.'.format( crayons.yellow(branch)), branch) @cli.command(short_help='Removes specified branch from the remote.') @click.argument('published_branch', required=False) @click.option('--verbose', is_flag=True, help='Enables verbose mode.') @click.option('--fake', is_flag=True, help='Show but do not invoke git commands.') @pass_scm def unpublish(scm, published_branch, verbose, fake): """Removes a published branch from the remote repository.""" scm.fake = fake scm.verbose = fake or verbose scm.repo_check(require_remote=True) branch = scm.fuzzy_match_branch(published_branch) if not branch: scm.display_available_branches() raise click.BadArgumentUsage('Please specify a branch to unpublish') branch_names = scm.get_branch_names(local=False) if branch not in branch_names: raise click.BadArgumentUsage( "Branch {} is not published. Use a branch that is published." .format(crayons.yellow(branch))) status_log(scm.unpublish_branch, 'Unpublishing {}.'.format( crayons.yellow(branch)), branch) @cli.command() @click.option('--verbose', is_flag=True, help='Enables verbose mode.') @click.option('--fake', is_flag=True, help='Show but do not invoke git commands.') @click.option('--hard', is_flag=True, help='Discard local changes.') @pass_scm def undo(scm, verbose, fake, hard): """Removes the last commit from history.""" scm.fake = fake scm.verbose = fake or verbose scm.repo_check() status_log(scm.undo, 'Last commit removed from history.', hard) @cli.command() @click.argument('wildcard_pattern', required=False) @pass_scm def branches(scm, wildcard_pattern): """Displays a list of branches.""" scm.repo_check() if wildcard_pattern: scm.display_available_branches(wildcard_pattern) else: scm.display_available_branches() def do_install(ctx, verbose, fake): """Installs legit git aliases.""" click.echo('The following git aliases will be installed:\n') aliases = cli.list_commands(ctx) if git_version() >= (2, 23, 0): click.echo( 'As git 2.23.0 introduces a new command "git switch", alias "switch"' ' will be skipped.\nUse "legit switch" instead.' ) aliases.remove('switch') output_aliases(aliases) program = program_path() if click.confirm('\n{}Install aliases above?'.format('FAKE ' if fake else ''), default=fake): for alias in aliases: cmd = '!{} {}'.format(program, alias) system_command = 'git config --global --replace-all alias.{} "{}"'.format(alias, cmd) verbose_echo(system_command, verbose, fake) if not fake: os.system(system_command) if not fake: click.echo("\nAliases installed.") else: click.echo("\nAliases will not be installed.") def do_uninstall(ctx, verbose, fake): """Uninstalls legit git aliases, including deprecated legit sub-commands.""" aliases = cli.list_commands(ctx) # Add deprecated aliases aliases.extend(['graft', 'harvest', 'sprout', 'resync', 'settings', 'install', 'uninstall']) for alias in aliases: system_command = 'git config --global --unset-all alias.{}'.format(alias) verbose_echo(system_command, verbose, fake) if not fake: os.system(system_command) if not fake: click.echo('\nThe following git aliases are uninstalled:\n') output_aliases(aliases) def do_edit_settings(fake): """Opens legit settings in editor.""" path = resources.user.open('config.ini').name click.echo('Legit Settings:\n') for (option, _, description) in legit_settings.config_defaults: click.echo(columns([crayons.yellow(option), 25], [description, None])) click.echo("") # separate settings info from os output if fake: click.echo(crayons.red('Faked! >>> edit {}'.format(path))) else: click.edit(filename=path) def handle_abort(aborted, type=None): click.echo('{} {}'.format(crayons.red('Error:'), aborted.message)) click.echo(str(aborted.log)) if type == 'merge': click.echo('Unfortunately, there was a merge conflict.' ' It has to be merged manually.') elif type == 'unpublish': click.echo( '''It seems that the remote branch is deleted. If `legit branches` still shows it as published, then probably the branch has been deleted at the remote by someone else. You can run `git fetch --prune` to update remote information. ''') raise click.Abort legit_settings.abort_handler = handle_abort legit-1.2.0.post0/legit/utils.py0000644000076500000240000000673713631173762016751 0ustar fmingstaff00000000000000import click from clint.textui import colored, columns import crayons import os import sys from .settings import legit_settings def status_log(func, message, *args, **kwargs): """Emits header message, executes a callable, and echoes the return strings.""" click.echo(message) log = func(*args, **kwargs) if log: out = [] for line in log.split('\n'): if not line.startswith('#'): out.append(line) click.echo(black('\n'.join(out))) def verbose_echo(str, verbose=False, fake=False): """Selectively output ``str``, with special formatting if ``fake`` is True""" verbose = fake or verbose if verbose: color = crayons.green prefix = '' if fake: color = crayons.red prefix = 'Faked!' click.echo(color('{} >>> {}'.format(prefix, str))) def output_aliases(aliases): """Display git aliases""" for alias in aliases: cmd = '!legit ' + alias click.echo(columns([colored.yellow('git ' + alias), 20], [cmd, None])) def order_manually(sub_commands): """Order sub-commands for display""" order = [ "switch", "sync", "publish", "unpublish", "undo", "branches", ] ordered = [] commands = dict(zip([cmd for cmd in sub_commands], sub_commands)) for k in order: ordered.append(commands.get(k, "")) if k in commands: del commands[k] # Add commands not present in `order` above for k in commands: ordered.append(commands[k]) return ordered def format_help(help): """Format the help string.""" help = help.replace('Options:', str(black('Options:', bold=True))) help = help.replace('Usage: legit', str('Usage: {}'.format(black('legit', bold=True)))) help = help.replace(' switch', str(crayons.green(' switch', bold=True))) help = help.replace(' sync', str(crayons.green(' sync', bold=True))) help = help.replace(' publish', str(crayons.green(' publish', bold=True))) help = help.replace(' unpublish', str(crayons.green(' unpublish', bold=True))) help = help.replace(' undo', str(crayons.green(' undo', bold=True))) help = help.replace(' branches', str(crayons.yellow(' branches', bold=True))) additional_help = \ """Usage Examples: Switch to specific branch: $ {} Sync current branch with remote: $ {} Sync current code with a specific remote branch: $ {} Publish current branch to remote: $ {} Publish a specific branch to remote: $ {} Unpublish a specific branch from remote: $ {} List branches matching wildcard pattern: $ {} Commands:""".format( crayons.red('legit switch '), crayons.red('legit sync'), crayons.red('legit sync '), crayons.red('legit publish'), crayons.red('legit publish '), crayons.red('legit unpublish '), crayons.red('legit branches []'), ) help = help.replace('Commands:', additional_help) return help def black(s, **kwargs): if legit_settings.allow_black_foreground: return crayons.black(s, **kwargs) else: return s def git_version(): """Return the git version tuple (major, minor, patch)""" from git import Git g = Git() return g.version_info def program_path(): result = os.path.abspath(sys.argv[0]) result = result.replace(os.sep, '/').replace(' ', '\\ ') return result legit-1.2.0.post0/legit/settings.py0000644000076500000240000000277513552057442017445 0ustar fmingstaff00000000000000# -*- coding: utf-8 -*- """ legit.config ~~~~~~~~~~~~~~~~~~ This module provides the Legit settings feature set. """ class Settings(object): _singleton = {} # attributes with defaults __attrs__ = tuple() def __init__(self, **kwargs): super(Settings, self).__init__() self.__dict__ = self._singleton def __call__(self, *args, **kwargs): # new instance of class to call r = self.__class__() # cache previous settings for __exit__ r.__cache = self.__dict__.copy() map(self.__cache.setdefault, self.__attrs__) # set new settings self.__dict__.update(*args, **kwargs) return r def __enter__(self): pass def __exit__(self, *args): # restore cached copy self.__dict__.update(self.__cache.copy()) del self.__cache def __getattribute__(self, key): if key in object.__getattribute__(self, '__attrs__'): try: return object.__getattribute__(self, key) except AttributeError: return None return object.__getattribute__(self, key) legit_settings = Settings() legit_settings.config_defaults = ( ('allow_black_foreground', 'True', 'Is the epic black foreground color allowed? Defaults to True.'), ('disable_colors', 'False', 'Y U NO FUN? Defaults to False.'), ) legit_settings.update_url = 'https://api.github.com/repos/frostming/legit/tags' legit_settings.forbidden_branches = ['HEAD'] legit-1.2.0.post0/legit/scm.py0000644000076500000240000002755313631173762016372 0ustar fmingstaff00000000000000# -*- coding: utf-8 -*- """ legit.scm ~~~~~~~~~ This module provides the main interface to Git. """ import fnmatch import os import sys from collections import namedtuple from operator import attrgetter import click from clint.textui import colored, columns import crayons from git import Repo from git.exc import GitCommandError, InvalidGitRepositoryError from .settings import legit_settings from .utils import black, status_log LEGIT_TEMPLATE = 'Legit: stashing before {0}.' Branch = namedtuple('Branch', ['name', 'is_published']) class SCMRepo(object): git = None repo = None remote = None verbose = False fake = False stash_index = None def __init__(self): self.git = os.environ.get('GIT_PYTHON_GIT_EXECUTABLE', 'git') try: self.repo = Repo(search_parent_directories=True) self.remote = self.get_remote() except InvalidGitRepositoryError: self.repo = None def git_exec(self, command, **kwargs): """Execute git commands""" from .cli import verbose_echo command.insert(0, self.git) if kwargs.pop('no_verbose', False): # used when git output isn't helpful to user verbose = False else: verbose = self.verbose verbose_echo(' '.join(command), verbose, self.fake) if not self.fake: result = self.repo.git.execute(command, **kwargs) else: if 'with_extended_output' in kwargs: result = (0, '', '') else: result = '' return result def repo_check(self, require_remote=False): if self.repo is None: click.echo('Not a git repository.') sys.exit(128) # TODO: no remote fail if not self.repo.remotes and require_remote: click.echo('No git remotes configured. Please add one.') sys.exit(128) # TODO: You're in a merge state. def stash_log(self, sync=False): if self.repo.is_dirty(): status_log(self.stash_it, 'Saving local changes.', sync=sync) def unstash_log(self, sync=False): self.stash_index = self.unstash_index(sync=sync) if self.stash_index: status_log(self.unstash_it, 'Restoring local changes.', sync=sync) def unstash_index(self, sync=False, branch=None): """Returns an unstash index if one is available.""" stash_list = self.git_exec(['stash', 'list'], no_verbose=True) if branch is None: branch = self.get_current_branch_name() for stash in stash_list.splitlines(): verb = 'syncing' if sync else 'switching' if ( (('Legit' in stash) and ('On {}:'.format(branch) in stash) and (verb in stash) ) or (('GitHub' in stash) and ('On {}:'.format(branch) in stash) and (verb in stash) ) ): return stash[7] def stash_it(self, sync=False): msg = 'syncing branch' if sync else 'switching branches' return self.git_exec( ['stash', 'save', '--include-untracked', LEGIT_TEMPLATE.format(msg)]) def unstash_it(self, sync=False): """ Unstashes changes from current branch for branch sync. Requires prior code setting self.stash_index. """ if self.stash_index is not None: return self.git_exec( ['stash', 'pop', 'stash@{{{0}}}'.format(self.stash_index)]) def smart_pull(self): """ 'git log --merges origin/master..master' """ branch = self.get_current_branch_name() self.git_exec(['fetch', self.remote.name]) return self.smart_merge('{}/{}'.format(self.remote.name, branch), self.smart_merge_enabled()) def smart_merge_enabled(self): reader = self.repo.config_reader() if reader.has_option('legit', 'smartMerge'): return reader.getboolean('legit', 'smartMerge') else: return True def smart_merge(self, branch, allow_rebase=True): from_branch = self.get_current_branch_name() merges = self.git_exec( ['log', '--merges', '{}..{}'.format(branch, from_branch)]) if allow_rebase: verb = 'merge' if merges.count('commit') else 'rebase' else: if self.pull_rebase(): verb = 'rebase' else: verb = 'merge' if verb != 'rebase' and self.pull_ff_only(): return self.git_exec([verb, '--ff-only', branch]) else: try: return self.git_exec([verb, branch]) except GitCommandError as why: log = self.git_exec([verb, '--abort']) abort('Merge failed. Reverting.', log='{}\n{}'.format(why, log), type='merge') def pull_rebase(self): reader = self.repo.config_reader() if reader.has_option('pull', 'rebase'): return reader.getboolean('pull', 'rebase') else: return False def pull_ff_only(self): reader = self.repo.config_reader() if reader.has_option('pull', 'ff'): if reader.get('pull', 'ff') == 'only': return True else: return False else: return False def push(self, branch=None): if branch is None: return self.git_exec(['push']) else: return self.git_exec(['push', self.remote.name, branch]) def checkout_branch(self, branch): """Checks out given branch.""" _, stdout, stderr = self.git_exec( ['checkout', branch], with_extended_output=True) return '\n'.join([stderr, stdout]) def unpublish_branch(self, branch): """Unpublishes given branch.""" try: return self.git_exec( ['push', self.remote.name, ':{}'.format(branch)]) except GitCommandError: _, _, log = self.git_exec( ['fetch', self.remote.name, '--prune'], with_extended_output=True) abort('Unpublish failed. Fetching.', log=log, type='unpublish') def publish_branch(self, branch): """Publishes given branch.""" return self.git_exec( ['push', '-u', self.remote.name, branch]) def undo(self, hard=False): """Makes last commit not exist""" if not self.fake: return self.repo.git.reset('HEAD^', working_tree=hard) else: click.echo(crayons.red('Faked! >>> git reset {}{}' .format('--hard ' if hard else '', 'HEAD^'))) return 0 def get_remote(self): self.repo_check() reader = self.repo.config_reader() # If there is no remote option in legit section, return default if reader.has_option('legit', 'remote'): remote_name = reader.get('legit', 'remote') if remote_name not in [r.name for r in self.repo.remotes]: if fallback_enabled(reader): return self.get_default_remote() else: click.echo('Remote "{}" does not exist!'.format(remote_name)) will_aborted = click.confirm( '\nPress `Y` to abort now,\n' + '`n` to use default remote and turn fallback on for this repo:') if will_aborted: click.echo('\nAborted. Please update your git configuration.') sys.exit(64) # EX_USAGE else: writer = self.repo.config_writer() writer.set_value('legit', 'remoteFallback', 'true') click.echo('\n`legit.RemoteFallback` changed to true for current repo.') return self.get_default_remote() else: return self.repo.remote(remote_name) else: return self.get_default_remote() def get_default_remote(self): if len(self.repo.remotes) == 0: return None else: return self.repo.remotes[0] def get_current_branch_name(self): """Returns current branch name""" return self.repo.head.ref.name def fuzzy_match_branch(self, branch): if not branch: return False all_branches = self.get_branch_names() if branch in all_branches: return branch def branch_fuzzy_match(b): return b.startswith(branch) possible_branches = list(filter(branch_fuzzy_match, all_branches)) if len(possible_branches) == 1: return possible_branches[0] return branch def get_branches(self, local=True, remote_branches=True, wildcard_pattern='*'): """Returns a list of local and remote branches matching wildcard_pattern.""" if not self.repo.remotes: remote_branches = False branches = [] if remote_branches: # Remote refs. try: for b in self.remote.refs: name = '/'.join(b.name.split('/')[1:]) if name not in legit_settings.forbidden_branches: branches.append(Branch(name, is_published=True)) except (IndexError, AssertionError): pass if local: # Local refs. for b in [h.name for h in self.repo.heads]: if (not remote_branches) or (b not in [br.name for br in branches]): if b not in legit_settings.forbidden_branches: branches.append(Branch(b, is_published=False)) matching_branch_names = fnmatch.filter( [branch.name for branch in branches], wildcard_pattern ) branches = [branch for branch in branches if branch.name in matching_branch_names] return sorted(branches, key=attrgetter('name')) def get_branch_names(self, local=True, remote_branches=True): branches = self.get_branches(local=local, remote_branches=remote_branches) return [b.name for b in branches] def display_available_branches(self, wildcard_pattern='*'): """Displays available branches.""" if not self.repo.remotes: remote_branches = False else: remote_branches = True branches = self.get_branches( local=True, remote_branches=remote_branches, wildcard_pattern=wildcard_pattern ) if not branches: click.echo(crayons.red('No branches available')) return branch_col = len(max([b.name for b in branches], key=len)) + 1 for branch in branches: try: branch_is_selected = (branch.name == self.get_current_branch_name()) except TypeError: branch_is_selected = False marker = '*' if branch_is_selected else ' ' color = colored.green if branch_is_selected else colored.yellow pub = '(published)' if branch.is_published else '(unpublished)' click.echo(columns( [colored.red(marker), 2], [color(branch.name, bold=True), branch_col], [black(pub), 14] )) # Instead of getboolean('legit', 'remoteFallback', fallback=False) # since getboolean in Python 2 does not have fallback argument. def fallback_enabled(reader): if reader.has_option('legit', 'remoteFallback'): return reader.getboolean('legit', 'remoteFallback') else: return False class Aborted(object): def __init__(self): self.message = None self.log = None def abort(message, log=None, type=None): a = Aborted() a.message = message a.log = log legit_settings.abort_handler(a, type=type) legit-1.2.0.post0/setup.cfg0000644000076500000240000000020013633166313015723 0ustar fmingstaff00000000000000[bdist_wheel] universal = 1 [tool:pytest] addopts = -v -x --ignore=setup.py --cov=legit [egg_info] tag_build = tag_date = 0 legit-1.2.0.post0/README.rst0000644000076500000240000000647313631173762015617 0ustar fmingstaff00000000000000.. -*-restructuredtext-*- Legit: Git for Humans ===================== Inspired by GitHub for Mac. The Concept ----------- `GitHub for Mac `_ is not just a Git client. This `comment `_ on Hacker News says it best: They haven't re-created the git CLI tool in a GUI, they've created something different. They've created a tool that makes Git more accessible. Little things like auto-stashing when you switch branches will confuse git veterans, but it will make Git much easier to grok for newcomers because of the assumptions it makes about your Git workflow. Why not bring this innovation back to the command line? The Interface ------------- ``switch `` Switches to specified branch. Defaults to current branch. Automatically stashes and unstashes any changes. (alias: ``sw``) ``sync []`` Synchronizes the given branch. Defaults to current branch. Stash, Fetch, Auto-Merge/Rebase, Push, and Unstash. You can only sync published branches. (alias: ``sy``) ``publish []`` Publishes specified branch to the remote. (alias: ``pub``) ``unpublish `` Removes specified branch from the remote. (alias: ``unp``) ``undo`` Un-does the last commit in git history. (alias: ``un``) ``branches []`` Display a list of available branches. Allows wildcard pattern matching of branch name. The Installation ---------------- .. image:: https://img.shields.io/pypi/v/legit.svg :target: https://pypi.python.org/pypi/legit/ .. image:: https://img.shields.io/travis/frostming/legit/master.svg :target: https://travis-ci.org/frostming/legit/ .. image:: https://img.shields.io/coveralls/github/frostming/legit.svg :target: https://coveralls.io/r/frostming/legit/ .. image:: https://repl.it/badge/github/frostming/legit :target: https://repl.it/github/frostming/legit From `PyPI `_ with the Python package manager:: pip install legit Or download a standalone Windows executable from `GitHub Releases `_. To install the cutting edge version from the git repository:: git clone https://github.com/frostming/legit.git cd legit python setup.py install Note: if you encountered `Permission denied`, prepend `sudo` before the `pip` or `python setup.py` command. You'll then have the wonderful ``legit`` command available. Run it within a repository. To view usage and examples, run ``legit`` with no commands or options:: legit To install the git aliases, run the following command:: legit --install To uninstall the git aliases, run the following command:: legit --uninstall Command Options --------------- All legit commands support ``--verbose`` and ``--fake`` options. In order to view the git commands invoked by legit, use the ``--verbose`` option:: legit sync --verbose If you want to see the git commands used by legit but don't want them invoked, use the ``--fake`` option:: legit publish --fake Caveats ------- - All remote operations are carried out by the remote identified in ``$ git config legit.remote remotename`` - If a ``stash pop`` merge fails, Legit stops. I'd like to add checking for a failed merge, and undo the command with friendly error reporting.