legit-0.4.1/0000755000076500000240000000000013047401101014444 5ustar kennethreitzstaff00000000000000legit-0.4.1/extra/0000755000076500000240000000000013047401101015567 5ustar kennethreitzstaff00000000000000legit-0.4.1/extra/bash-completion/0000755000076500000240000000000013047401101020653 5ustar kennethreitzstaff00000000000000legit-0.4.1/extra/bash-completion/legit0000644000076500000240000000510013047375375021725 0ustar kennethreitzstaff00000000000000_legit() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="branches graft publish sprout switch sync resync unpublish harvest" 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 ;; sp|sprout) COMPREPLY=( $(compgen -W "${running}" -- ${cur}) ) return 0 ;; gr|graft) COMPREPLY=( $(compgen -W "${running}" -- ${cur}) ) return 0 ;; ha|hv|har|harvest) COMPREPLY=( $(compgen -W "${running}" -- ${cur}) ) return 0 ;; pub|publish) COMPREPLY=( $(compgen -W "${running}" -- ${cur}) ) return 0 ;; unp|unpublish|rs|resync) 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_resync { _complete_with_git_branch; } function _git_switch { _complete_with_git_branch; } function _git_sprout { _complete_with_git_branch; } function _git_graft { _complete_with_git_branch; } function _git_harvest { _complete_with_git_branch; } function _git_publish { _complete_with_git_branch; } complete -F _git_sync git-sync complete -F _git_resync git-resync complete -F _git_switch git-switch complete -F _git_sprout git-sprout complete -F _git_graft git-graft complete -F _git_harvest git-harvest complete -F _git_publish git-publish complete -F _git_unpublish git-unpublish legit-0.4.1/extra/man/0000755000076500000240000000000013047401101016342 5ustar kennethreitzstaff00000000000000legit-0.4.1/extra/man/legit.10000644000076500000240000000703013047375375017557 0ustar kennethreitzstaff00000000000000.\" 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. .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 \fBresync \fP Stashes unstaged changes, Fetches, Auto\-Merge/Rebase upstream data from specified upstream branch, Performs smart pull+merge for current branch, Pushes local commits up, and Unstashes changes. Default upstream branch is \(aqmaster\(aq. .TP .B \fBswitch \fP Switches to specified branch. Defaults to current branch. Automatically stashes and unstashes any changes. .TP .B \fBsprout [] \fP Creates a new branch off of the specified branch. Swiches to it immediately. .TP .B \fBharvest [] \fP Auto\-Merge/Rebase of specified branch changes into the second branch. .TP .B \fBgraft \fP Auto\-Merge/Rebase of specified branch into the second branch. Immediately removes specified branch. You can only graft unpublished branches. .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-0.4.1/extra/zsh-completion/0000755000076500000240000000000013047401101020542 5ustar kennethreitzstaff00000000000000legit-0.4.1/extra/zsh-completion/_legit0000644000076500000240000001016613047375375021763 0ustar kennethreitzstaff00000000000000#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.' 'sync: Synchronizes the given branch. Defaults to current branch. Stash, Fetch, Auto-Merge/Rebase, Push, and Unstash. You can only sync published branches.' 'resync: Re-synchronize current branch with specified upstream branch.' 'switch: Switches to specified branch. Defaults to current branch. Automatically stashes and unstashes any changes.' 'sprout: Creates a new branch off of the specified branch. Switches to it immediately.' 'graft: Merges specified branch into the second branch, and removes it. You can only graft unpublished branches.' 'publish: Publishes specified branch to the remote.' 'unpublish: Removes specified branch from the remote.' 'harvest: Syncs a branch with a given branch. Defaults to current.' '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|resync) _arguments \ ':branch:__git_branch_names' ;; (sp|sprout) _arguments \ '1:branch:__git_branch_names' \ '2:new-branch' ;; (gr|graft) _arguments \ '1:branch:__git_branch_names' \ '2:into-branch:__git_branch_names' ;; (ha|hv|har|harvest) _arguments \ '1:from-branch:__git_branch_names' \ '2:to-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-0.4.1/legit/0000755000076500000240000000000013047401101015550 5ustar kennethreitzstaff00000000000000legit-0.4.1/legit/__init__.py0000644000076500000240000000005413047375375017707 0ustar kennethreitzstaff00000000000000# -*- coding: utf-8 -*- from .core import *legit-0.4.1/legit/bootstrap.py0000644000076500000240000000260313047375375020167 0ustar kennethreitzstaff00000000000000# -*- coding: utf-8 -*- """ legit.bootstrap ~~~~~~~~~~~~~~~ This module boostraps the Legit runtime. """ from six.moves import configparser import clint.textui.colored from clint import resources from clint.textui import colored from .settings import 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() 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 settings.config_defaults: if not config.has_option('legit', k): modified = True config.set('legit', k, v) setattr(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(settings, k, val) if modified: config_file = resources.user.open('config.ini', 'w') config.write(config_file) if settings.disable_colors: clint.textui.colored.DISABLE_COLOR = True legit-0.4.1/legit/cli.py0000644000076500000240000003516513047400433016712 0ustar kennethreitzstaff00000000000000# -*- coding: utf-8 -*- """ legit.cli ~~~~~~~~~ This module provides the CLI interface to legit. """ import os import sys from subprocess import call from time import sleep import difflib import clint.resources try: from clint import Args args = Args() except ImportError: from clint import args from clint.eng import join as eng_join from clint.textui import colored, puts, columns, indent from .core import __version__ from .settings import settings from .helpers import is_lin, is_osx, is_win from .scm import * def black(s): if settings.allow_black_foreground: return colored.black(s) else: return s.encode('utf-8') # -------- # Dispatch # -------- def main(): """Primary Legit command dispatch.""" command = Command.lookup(args.get(0)) if command: arg = args.get(0) args.remove(arg) command.__call__(args) sys.exit() elif args.contains(('-h', '--help')): display_help() sys.exit() elif args.contains(('-v', '--version')): display_version() sys.exit() elif len(args) == 0: display_help() sys.exit() else: if settings.git_transparency: # Send everything to git git_args = list(sys.argv) if settings.git_transparency is True: settings.git_transparency = os.environ.get("GIT_PYTHON_GIT_EXECUTABLE", 'git') git_args[0] = settings.git_transparency sys.exit(call(' '.join(git_args), shell=True)) else: show_error(colored.red('Unknown command {0}'.format(args.get(0)))) display_info() sys.exit(1) def show_error(msg): sys.stdout.flush() sys.stderr.write(msg + '\n') # ------- # Helpers # ------- def status_log(func, message, *args, **kwargs): """Executes a callable with a header message.""" print(message) log = func(*args, **kwargs) if log: out = [] for line in log.split('\n'): if not line.startswith('#'): out.append(line) print(black('\n'.join(out))) def switch_to(branch): """Runs the cmd_switch command with given branch arg.""" switch_args = args.copy switch_args._args = [branch] return cmd_switch(switch_args) def fuzzy_match_branch(branch): if not branch: return False all_branches = 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 # -------- # Commands # -------- def cmd_switch(args): """Legit Switch command.""" to_branch = args.get(0) to_branch = fuzzy_match_branch(to_branch) if repo.is_dirty(): status_log(stash_it, 'Saving local changes.') status_log(checkout_branch, 'Switching to {0}.'.format( colored.yellow(to_branch)), to_branch) if unstash_index(): status_log(unstash_it, 'Restoring local changes.') def cmd_resync(args): """Stashes unstaged changes, Fetches, Auto-Merge/Rebase upstream data from specified upstream branch, Performs smart pull+merge for current branch, Pushes local commits up, and Unstashes changes. Default upstream branch is 'master'. """ if args.get(0): upstream = fuzzy_match_branch(args.get(0)) if upstream: is_external = True original_branch = get_current_branch_name() else: print("{0} doesn't exist. Use a branch that does.".format( colored.yellow(args.get(0)))) sys.exit(1) else: upstream = "master" original_branch = get_current_branch_name() if repo.is_dirty(): status_log(stash_it, 'Saving local changes.', sync=True) # Update upstream branch switch_to(upstream) status_log(smart_pull, 'Pulling commits from the server.') # Update original branch with upstream switch_to(original_branch) status_log(smart_merge, 'Grafting commits from {0}.'.format( colored.yellow(upstream)), upstream, allow_rebase=False) if unstash_index(sync=True): status_log(unstash_it, 'Restoring local changes.', sync=True) # Sync original_branch status_log(smart_pull, 'Pulling commits from the server.') status_log(push, 'Pushing commits to the server.', original_branch) def cmd_sync(args): """Stashes unstaged changes, Fetches remote data, Performs smart pull+merge, Pushes local commits up, and Unstashes changes. Defaults to current branch. """ if args.get(0): # Optional branch specifier. branch = fuzzy_match_branch(args.get(0)) if branch: is_external = True original_branch = get_current_branch_name() else: print("{0} doesn't exist. Use a branch that does.".format( colored.yellow(args.get(0)))) sys.exit(1) else: # Sync current branch. branch = get_current_branch_name() is_external = False if branch in get_branch_names(local=False): if is_external: switch_to(branch) if repo.is_dirty(): status_log(stash_it, 'Saving local changes.', sync=True) status_log(smart_pull, 'Pulling commits from the server.') status_log(push, 'Pushing commits to the server.', branch) if unstash_index(sync=True): status_log(unstash_it, 'Restoring local changes.', sync=True) if is_external: switch_to(original_branch) else: print('{0} has not been published yet.'.format( colored.yellow(branch))) sys.exit(1) def cmd_undo(args): """Makes last commit not exist. --hard for """ repo.git.reset('HEAD^') print('Last commit removed from history.') def cmd_publish(args): """Pushes an unpublished branch to a remote repository.""" branch = fuzzy_match_branch(args.get(0)) if not branch: branch = get_current_branch_name() display_available_branches() if args.get(0) is None: print("Using current branch {0}".format(colored.yellow(branch))) else: print("Branch {0} not found, using current branch {1}".format(colored.red(args.get(0)),colored.yellow(branch))) branch_names = get_branch_names(local=False) if branch in branch_names: print("{0} is already published. Use a branch that isn't.".format( colored.yellow(branch))) sys.exit(1) status_log(publish_branch, 'Publishing {0}.'.format( colored.yellow(branch)), branch) def cmd_unpublish(args): """Removes a published branch from the remote repository.""" branch = fuzzy_match_branch(args.get(0)) if not branch: print('Please specify a branch to unpublish:') display_available_branches() sys.exit(64) # EX_USAGE branch_names = get_branch_names(local=False) if branch not in branch_names: print("{0} isn't published. Use a branch that is.".format( colored.yellow(branch))) sys.exit(1) status_log(unpublish_branch, 'Unpublishing {0}.'.format( colored.yellow(branch)), branch) def cmd_branches(args): """Displays available branches.""" display_available_branches() def cmd_settings(args): """Opens legit settings in editor.""" path = clint.resources.user.open('config.ini').name print('Legit Settings:\n') for (option, _, description) in settings.config_defaults: print(columns([colored.yellow(option), 25], [description, None])) print('\nSee {0} for more details.'.format(settings.config_url)) sleep(0.35) if is_osx: editor = os.environ.get('EDITOR') or os.environ.get('VISUAL') or 'open' os.system("{0} '{1}'".format(editor, path)) elif is_lin: editor = os.environ.get('EDITOR') or os.environ.get('VISUAL') or 'pico' os.system("{0} '{1}'".format(editor, path)) elif is_win: os.system("'{0}'".format(path)) else: print("Edit '{0}' to manage Legit settings.\n".format(path)) sys.exit() def cmd_install(args): """Installs legit git aliases.""" aliases = [ 'branches', 'publish', 'unpublish', 'sync', 'switch', 'resync', 'undo' ] print('The following git aliases have been installed:\n') for alias in aliases: cmd = '!legit ' + alias os.system('git config --global --replace-all alias.{0} "{1}"'.format(alias, cmd)) print(columns(['', 1], [colored.yellow('git ' + alias), 20], [cmd, None])) sys.exit() def cmd_help(args): """Display help for individual commands.""" command = args.get(0) help(command) # ----- # Views # ----- def help(command, to_stderr=False): if command == None: command = 'help' cmd = Command.lookup(command) usage = cmd.usage or '' alias = 'alias: %s\n\n' % ', '.join(cmd.short) if cmd.short else '' help = columns([cmd.help, 60]) or '' help_text = '%s\n\n%s%s' % (usage, alias, help) if to_stderr: show_error(help_text) else: print(help_text) def display_available_branches(): """Displays available branches.""" branches = get_branches() if not branches: print(colored.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 == 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)' print(columns( [colored.red(marker), 2], [color(branch.name), branch_col], [black(pub), 14] )) def sort_with_similarity(iterable, key=None): """Sort string list with similarity following original order.""" if key is None: key = lambda x: x ordered = [] left_iterable = dict(zip([key(elm) for elm in iterable], iterable)) for k in left_iterable.keys(): if k not in left_iterable: continue ordered.append(left_iterable[k]) del left_iterable[k] # find close named iterable close_iterable = difflib.get_close_matches(k, left_iterable.keys()) for close in close_iterable: ordered.append(left_iterable[close]) del left_iterable[close] return ordered def display_info(): """Displays Legit informatics.""" puts('{0}. {1}\n'.format( colored.red('legit'), black('A Kenneth Reitz Project') )) puts('Usage: {0}\n'.format(colored.blue('legit '))) puts('Commands:\n') commands = Command.all_commands() for command in sort_with_similarity(commands, key=lambda x:x.name): usage = command.usage or command.name detail = command.help or '' puts(colored.green(usage)) with indent(2): puts(first_sentence(detail)) def first_sentence(s): pos = s.find('. ') if pos < 0: pos = len(s) - 1 return s[:pos + 1] def display_help(): """Displays Legit help.""" display_info() def display_version(): """Displays Legit version/release.""" puts('{0} v{1}'.format( colored.yellow('legit'), __version__ )) def handle_abort(aborted, type=None): print('{0} {1}'.format(colored.red('Error:'), aborted.message)) print(black(str(aborted.log))) if type == 'merge': print('Unfortunately, there was a merge conflict.' ' It has to be merged manually.') elif type == 'unpublish': print('It seems that the remote branch has been already deleted.') sys.exit(1) settings.abort_handler = handle_abort class Command(object): COMMANDS = {} SHORT_MAP = {} @classmethod def register(klass, command): klass.COMMANDS[command.name] = command if command.short: for short in command.short: klass.SHORT_MAP[short] = command @classmethod def lookup(klass, name): if name in klass.SHORT_MAP: return klass.SHORT_MAP[name] if name in klass.COMMANDS: return klass.COMMANDS[name] else: return None @classmethod def all_commands(klass): return sorted(klass.COMMANDS.values(), key=lambda cmd: cmd.name) def __init__(self, name=None, short=None, fn=None, usage=None, help=None): self.name = name self.short = short self.fn = fn self.usage = usage self.help = help def __call__(self, *args, **kw_args): return self.fn(*args, **kw_args) def def_cmd(name=None, short=None, fn=None, usage=None, help=None): command = Command(name=name, short=short, fn=fn, usage=usage, help=help) Command.register(command) def_cmd( name='branches', fn=cmd_branches, usage='branches', help='Get a nice pretty list of branches.') def_cmd( name='help', short=['h'], fn=cmd_help, usage='help ', help='Displays help for legit command.') def_cmd( name='install', fn=cmd_install, usage='install', help='Installs legit git aliases.') def_cmd( name='publish', short=['pub'], fn=cmd_publish, usage='publish []', help='Publishes specified branch to the remote.') def_cmd( name='settings', fn=cmd_settings, usage='settings', help='Opens legit settings in a text editor.') def_cmd( name='switch', short=['sw'], fn=cmd_switch, usage='switch ', help=('Switches to specified branch. Automatically stashes and unstashes ' 'any changes.')) def_cmd( name='sync', short=['sy'], fn=cmd_sync, usage='sync ', help=('Synchronizes the given branch. Defaults to current branch. Stash, ' 'Fetch, Auto-Merge/Rebase, Push, and Unstash.')) def_cmd( name='resync', short=['rs'], fn=cmd_resync, usage='resync ', help=('Re-synchronize current branch with specified upstream branch. ' "Default upstream branch is 'master'. " 'Fetch, Auto-Merge/Rebase for upstream, ' 'Fetch, Auto-Merge/Rebase, Push, and Unstash for current branch.')) def_cmd( name='unpublish', short=['unp'], fn=cmd_unpublish, usage='unpublish ', help='Removes specified branch from the remote.') def_cmd( name='undo', short=['un'], fn=cmd_undo, usage='undo', help='Removes the last commit from history.') legit-0.4.1/legit/core.py0000644000076500000240000000033713047401003017056 0ustar kennethreitzstaff00000000000000# -*- coding: utf-8 -*- """ legit.core ~~~~~~~~~~ This module provides the basic functionality of legit. """ from . import bootstrap del bootstrap __version__ = '0.4.1' __author__ = 'Kenneth Reitz' __license__ = 'BSD' legit-0.4.1/legit/helpers.py0000644000076500000240000000037513047375375017620 0ustar kennethreitzstaff00000000000000# -*- coding: utf-8 -*- """ legit.helpers ~~~~~~~~~~~~~ Various Python helpers. """ import os import platform _platform = platform.system().lower() is_osx = (_platform == 'darwin') is_win = (_platform == 'windows') is_lin = (_platform == 'linux') legit-0.4.1/legit/scm.py0000644000076500000240000001523313047375375016737 0ustar kennethreitzstaff00000000000000# -*- coding: utf-8 -*- """ legit.scm ~~~~~~~~~ This module provides the main interface to Git. """ import os import sys import subprocess from collections import namedtuple from operator import attrgetter from git import Repo from git.exc import GitCommandError, InvalidGitRepositoryError from .settings import settings LEGIT_TEMPLATE = 'Legit: stashing before {0}.' git = os.environ.get("GIT_PYTHON_GIT_EXECUTABLE", 'git') Branch = namedtuple('Branch', ['name', 'is_published']) 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 settings.abort_handler(a, type=type) def repo_check(require_remote=False): if repo is None: print('Not a git repository.') sys.exit(128) # TODO: no remote fail if not repo.remotes and require_remote: print('No git remotes configured. Please add one.') sys.exit(128) # TODO: You're in a merge state. def stash_it(sync=False): repo_check() msg = 'syncing branch' if sync else 'switching branches' return repo.git.execute([git, 'stash', 'save', '--include-untracked', LEGIT_TEMPLATE.format(msg)]) def unstash_index(sync=False, branch=None): """Returns an unstash index if one is available.""" repo_check() stash_list = repo.git.execute([git, 'stash', 'list']) if branch is None: branch = get_current_branch_name() for stash in stash_list.splitlines(): verb = 'syncing' if sync else 'switching' if ( (('Legit' in stash) and ('On {0}:'.format(branch) in stash) and (verb in stash)) or (('GitHub' in stash) and ('On {0}:'.format(branch) in stash) and (verb in stash)) ): return stash[7] def unstash_it(sync=False, branch=None): """Unstashes changes from current branch for branch sync.""" repo_check() stash_index = unstash_index(sync=sync, branch=branch) if stash_index is not None: return repo.git.execute([git, 'stash', 'pop', 'stash@{{{0}}}'.format(stash_index)]) def fetch(): repo_check() return repo.git.execute([git, 'fetch', remote.name]) def smart_pull(): 'git log --merges origin/master..master' repo_check() branch = get_current_branch_name() fetch() return smart_merge('{0}/{1}'.format(remote.name, branch)) def smart_merge(branch, allow_rebase=True): repo_check() from_branch = get_current_branch_name() merges = repo.git.execute([git, 'log', '--merges', '{0}..{1}'.format(branch, from_branch)]) if allow_rebase: verb = 'merge' if merges.count('commit') else 'rebase' else: verb = 'merge' try: return repo.git.execute([git, verb, branch]) except GitCommandError as why: log = repo.git.execute([git, verb, '--abort']) abort('Merge failed. Reverting.', log='{0}\n{1}'.format(why, log), type='merge') def push(branch=None): repo_check() if branch is None: return repo.git.execute([git, 'push']) else: return repo.git.execute([git, 'push', remote.name, branch]) def checkout_branch(branch): """Checks out given branch.""" repo_check() _, stdout, stderr = repo.git.execute([git, 'checkout', branch], with_extended_output=True) return '\n'.join([stderr, stdout]) def sprout_branch(off_branch, branch): """Checks out given branch.""" repo_check() return repo.git.execute([git, 'checkout', off_branch, '-b', branch]) def graft_branch(branch): """Merges branch into current branch, and deletes it.""" repo_check() log = [] try: msg = repo.git.execute([git, 'merge', '--no-ff', branch]) log.append(msg) except GitCommandError as why: log = repo.git.execute([git,'merge', '--abort']) abort('Merge failed. Reverting.', log='{0}\n{1}'.format(why, log), type='merge') out = repo.git.execute([git, 'branch', '-D', branch]) log.append(out) return '\n'.join(log) def unpublish_branch(branch): """Unpublishes given branch.""" repo_check() try: return repo.git.execute([git, 'push', remote.name, ':{0}'.format(branch)]) except GitCommandError: _, _, log = repo.git.execute([git, 'fetch', remote.name, '--prune'], with_extended_output=True) abort('Unpublish failed. Fetching.', log=log, type='unpublish') def publish_branch(branch): """Publishes given branch.""" repo_check() return repo.git.execute([git, 'push', '-u', remote.name, branch]) def get_repo(): """Returns the current Repo, based on path.""" try: return Repo(search_parent_directories=True) except InvalidGitRepositoryError: pass def get_remote(): repo_check(require_remote=True) reader = repo.config_reader() # If there is no remote option in legit section, return default if not reader.has_option('legit', 'remote'): return repo.remotes[0] remote_name = reader.get('legit', 'remote') if not remote_name in [r.name for r in repo.remotes]: raise ValueError('Remote "{0}" does not exist! Please update your git ' 'configuration.'.format(remote_name)) return repo.remote(remote_name) def get_current_branch_name(): """Returns current branch name""" repo_check() return repo.head.ref.name def get_branches(local=True, remote_branches=True): """Returns a list of local and remote branches.""" repo_check() # print local branches = [] if remote_branches: # Remote refs. try: for b in remote.refs: name = '/'.join(b.name.split('/')[1:]) if name not in 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 repo.heads]: if b not in [br.name for br in branches] or not remote_branches: if b not in settings.forbidden_branches: branches.append(Branch(b, is_published=False)) return sorted(branches, key=attrgetter('name')) def get_branch_names(local=True, remote_branches=True): repo_check() branches = get_branches(local=local, remote_branches=remote_branches) return [b.name for b in branches] repo = get_repo() if repo: # in git repository remote = get_remote() legit-0.4.1/legit/settings.py0000644000076500000240000000344313047375375020015 0ustar kennethreitzstaff00000000000000# -*- 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) settings = Settings() settings.config_defaults = ( ('check_for_updates', 'True', 'Are update checks allowed? Defaults to True.'), ('allow_black_foreground', 'True', 'Is the epic black foreground color allowed? Defaults to True.'), ('git_transparency', 'False', 'Send unknown commands to Git? Defaults to False.'), ('disable_colors', 'False', 'Y U NO FUN? Defaults to False.'), ('last_update_check', '', 'Date of the last update check.')) settings.config_url = 'http://git-legit.org/config' settings.update_url = 'https://api.github.com/repos/kennethreitz/legit/tags' settings.forbidden_branches = ['HEAD',]legit-0.4.1/legit.egg-info/0000755000076500000240000000000013047401101017242 5ustar kennethreitzstaff00000000000000legit-0.4.1/legit.egg-info/dependency_links.txt0000644000076500000240000000000113047401101023310 0ustar kennethreitzstaff00000000000000 legit-0.4.1/legit.egg-info/entry_points.txt0000644000076500000240000000005213047401101022535 0ustar kennethreitzstaff00000000000000[console_scripts] legit = legit.cli:main legit-0.4.1/legit.egg-info/PKG-INFO0000644000076500000240000000764313047401101020351 0ustar kennethreitzstaff00000000000000Metadata-Version: 1.1 Name: legit Version: 0.4.1 Summary: Git for Humans. Home-page: https://github.com/kennethreitz/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 ------------- ``branches`` Get a nice pretty list of available branches. ``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``) ``resync `` Stashes unstaged changes, Fetches, Auto-Merge/Rebase upstream data from specified upstream branch, Performs smart pull+merge for current branch, Pushes local commits up, and Unstashes changes. Default upstream branch is 'master'. (alias: ``rs``) ``switch `` Switches to specified branch. Defaults to current branch. Automatically stashes and unstashes any changes. (alias: ``sw``) ``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. ``install`` Installs legit git aliases. ``help`` Displays help for legit command. (alias: ``h``) The Installation ---------------- .. image:: https://img.shields.io/pypi/v/legit.svg :target: https://pypi.python.org/pypi/legit/ From `PyPI `_ with the Python package manager:: pip install legit Or download a standalone Windows executable from `GitHub Releases `_. You'll then have the wonderful ``legit`` command available. Run it within a repository. To install the git aliases, run the following command:: legit install 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: 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.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 legit-0.4.1/legit.egg-info/requires.txt0000644000076500000240000000020313047401101021635 0ustar kennethreitzstaff00000000000000appdirs==1.4.0 args==0.1.0 clint==0.5.1 gitdb2==2.0.0 GitPython==2.1.1 packaging==16.8 pyparsing==2.1.10 six==1.10.0 smmap2==2.0.1 legit-0.4.1/legit.egg-info/SOURCES.txt0000644000076500000240000000064413047401101021132 0ustar kennethreitzstaff00000000000000LICENSE MANIFEST.in README.rst reqs.txt 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/helpers.py legit/scm.py legit/settings.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-0.4.1/legit.egg-info/top_level.txt0000644000076500000240000000000613047401101021770 0ustar kennethreitzstaff00000000000000legit legit-0.4.1/LICENSE0000644000076500000240000000273413047375375015506 0ustar kennethreitzstaff00000000000000Copyright (c) 2013, Kenneth Reitz 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-0.4.1/MANIFEST.in0000644000076500000240000000007613047375375016234 0ustar kennethreitzstaff00000000000000include reqs.txt README.rst LICENSE recursive-include extra * legit-0.4.1/PKG-INFO0000644000076500000240000000764313047401101015553 0ustar kennethreitzstaff00000000000000Metadata-Version: 1.1 Name: legit Version: 0.4.1 Summary: Git for Humans. Home-page: https://github.com/kennethreitz/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 ------------- ``branches`` Get a nice pretty list of available branches. ``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``) ``resync `` Stashes unstaged changes, Fetches, Auto-Merge/Rebase upstream data from specified upstream branch, Performs smart pull+merge for current branch, Pushes local commits up, and Unstashes changes. Default upstream branch is 'master'. (alias: ``rs``) ``switch `` Switches to specified branch. Defaults to current branch. Automatically stashes and unstashes any changes. (alias: ``sw``) ``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. ``install`` Installs legit git aliases. ``help`` Displays help for legit command. (alias: ``h``) The Installation ---------------- .. image:: https://img.shields.io/pypi/v/legit.svg :target: https://pypi.python.org/pypi/legit/ From `PyPI `_ with the Python package manager:: pip install legit Or download a standalone Windows executable from `GitHub Releases `_. You'll then have the wonderful ``legit`` command available. Run it within a repository. To install the git aliases, run the following command:: legit install 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: 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.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 legit-0.4.1/README.rst0000644000076500000240000000501713047401043016143 0ustar kennethreitzstaff00000000000000.. -*-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 ------------- ``branches`` Get a nice pretty list of available branches. ``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``) ``resync `` Stashes unstaged changes, Fetches, Auto-Merge/Rebase upstream data from specified upstream branch, Performs smart pull+merge for current branch, Pushes local commits up, and Unstashes changes. Default upstream branch is 'master'. (alias: ``rs``) ``switch `` Switches to specified branch. Defaults to current branch. Automatically stashes and unstashes any changes. (alias: ``sw``) ``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. ``install`` Installs legit git aliases. ``help`` Displays help for legit command. (alias: ``h``) The Installation ---------------- .. image:: https://img.shields.io/pypi/v/legit.svg :target: https://pypi.python.org/pypi/legit/ From `PyPI `_ with the Python package manager:: pip install legit Or download a standalone Windows executable from `GitHub Releases `_. You'll then have the wonderful ``legit`` command available. Run it within a repository. To install the git aliases, run the following command:: legit install 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. legit-0.4.1/reqs.txt0000644000076500000240000000020313047400631016161 0ustar kennethreitzstaff00000000000000appdirs==1.4.0 args==0.1.0 clint==0.5.1 gitdb2==2.0.0 GitPython==2.1.1 packaging==16.8 pyparsing==2.1.10 six==1.10.0 smmap2==2.0.1 legit-0.4.1/setup.cfg0000644000076500000240000000010313047401101016257 0ustar kennethreitzstaff00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 legit-0.4.1/setup.py0000644000076500000240000000371213047401000016157 0ustar kennethreitzstaff00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import os import sys from setuptools import setup, find_packages # Always prefer setuptools over distutils from codecs import open # To use a consistent encoding APP_NAME = 'legit' APP_SCRIPT = './legit_r' VERSION = '0.4.1' # Grab requirements. with open('reqs.txt') as f: required = f.readlines() settings = dict() # Publish Helper. if sys.argv[-1] == 'publish': os.system('python setup.py sdist bdist_wheel upload') 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': import py2exe sys.argv.append('py2exe') settings.update( console=[{'script': APP_SCRIPT}], zipfile = None, options = { 'py2exe': { 'compressed': 1, 'optimize': 0, 'bundle_files': 1}}) 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/kennethreitz/legit', packages= ['legit',], install_requires=required, license='BSD', 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.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', ], entry_points={ 'console_scripts': [ 'legit = legit.cli:main', ], } ) setup(**settings)