Skip to content
This repository has been archived by the owner on May 29, 2019. It is now read-only.

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
spderosso committed Feb 10, 2015
2 parents fa418b1 + c2e304a commit 848a3d3
Show file tree
Hide file tree
Showing 13 changed files with 163 additions and 245 deletions.
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
gitpylib
=========

[![PyPI version](https://badge.fury.io/py/gitpylib.svg)](
http://badge.fury.io/py/gitpylib)

Python library for Git.

Has methods for the most frequently used Git features; abstracts the user of the
library from the burden of having to know which is the correct command to use to
perform some Git operation.
gitpylib is a lightweight wrapper of the `git` command.

**As of 02/2015 gitpylib has been phased out and merged into
[Gitless](https://github.com/sdg-mit/gitless "Gitless").**

Install
-------

Via `pip` (the Python Package Manager):

$> pip install gitless
6 changes: 6 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ Gitpylib's Release Notes
========================


10th Feb 2015 - 0.7
-------------------

* Bug fixes.


25th Mar 2014 - 0.6
-------------------

Expand Down
39 changes: 21 additions & 18 deletions gitpylib/branch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
"""Module for dealing with Git branches."""


import collections
import re

from . import common


BranchStatus = collections.namedtuple(
'BranchStatus', ['name', 'is_current', 'tracks'])

SUCCESS = 1
UNFETCHED_OBJECT = 2
INVALID_NAME = 3
Expand All @@ -26,7 +30,7 @@ def checkout(name):
Returns:
SUCCESS or NONEXISTENT_BRANCH
"""
ok, _, _ = common.git_call('checkout %s' % name)
ok, _, _ = common.git_call('checkout {0}'.format(name))
if not ok:
return NONEXISTENT_BRANCH
return SUCCESS
Expand Down Expand Up @@ -63,7 +67,7 @@ def force_delete(name):
Returns:
SUCCESS or NONEXISTENT_BRANCH
"""
ok, _, _ = common.git_call('branch -D %s' % name)
ok, _, _ = common.git_call('branch -D {0}'.format(name))
if not ok:
return NONEXISTENT_BRANCH
return SUCCESS
Expand All @@ -83,27 +87,26 @@ def status(name):
name: the name of the branch to status.
Returns:
a tuple (exists, is_current, tracks) where exists and is_current are
boolean values and tracks is a string representing the remote branch it
tracks (in the format 'remote_name/remote_branch') or None if it is a local
branch.
None if the branch doesn't exist or a namedtuple (name, is_current, tracks)
where is_current is a boolean value and tracks is a string representing the
remote branch it tracks (in the format 'remote_name/remote_branch') or None
if it is a local branch.
"""
out, _ = common.safe_git_call('branch --list -vv %s' % name)
out, _ = common.safe_git_call('branch --list -vv {0}'.format(name))
if not out:
return (False, False, None)
return None

_, is_current, tracks = _parse_output(out)
return (True, is_current, tracks)
return _parse_output(out)


def status_all():
"""Get the status of all existing branches.
Yields:
tuples of the form (name, is_current, tracks) where is_current is a boolean
value and tracks is a string representing the remote branch it tracks (in
the format 'remote_name/remote_branch') or None if it is a local branch.
name could be equal to '(no branch)' if the user is in no branch.
namedtuples of the form (name, is_current, tracks) where is_current is a
boolean value and tracks is a string representing the remote branch it
tracks (in the format 'remote_name/remote_branch') or None if it is a local
branch. name could be equal to '(no branch)' if the user is in no branch.
"""
out, _ = common.safe_git_call('branch --list -vv')
for b in out.splitlines():
Expand All @@ -118,7 +121,7 @@ def set_upstream(branch, upstream_branch):
upstream_branch: the upstream branch.
"""
ok, _, _ = common.git_call(
'branch --set-upstream %s %s' % (branch, upstream_branch))
'branch --set-upstream {0} {1}'.format(branch, upstream_branch))

if not ok:
return UNFETCHED_OBJECT
Expand All @@ -132,7 +135,7 @@ def unset_upstream(branch):
Args:
branch: the branch to unset its upstream.
"""
common.git_call('branch --unset-upstream %s' % branch)
common.git_call('branch --unset-upstream {0}'.format(branch))
return SUCCESS


Expand All @@ -151,7 +154,7 @@ def _parse_output(out):
# the branch followed by the sha1, optionally followed by some remote tracking
# info (between brackets) and finally the message of the last commit.
if out.startswith('* (no branch)'):
return ('(no branch)', True, None)
return BranchStatus('(no branch)', True, None)

pattern = r'([\*| ]) ([^\s]+)[ ]+\w+ (.+)'
result = re.match(pattern, out)
Expand All @@ -166,4 +169,4 @@ def _parse_output(out):
tracks = track_info.split(':')[0]
else:
tracks = track_info
return (result.group(2), result.group(1) == '*', tracks)
return BranchStatus(result.group(2), result.group(1) == '*', tracks)
26 changes: 15 additions & 11 deletions gitpylib/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ def safe_git_call(cmd):
ok, out, err = git_call(cmd)
if ok:
return out, err
raise Exception('%s failed: out is %s, err is %s' % (cmd, out, err))
raise Exception('{0} failed: out is {1}, err is {2}'.format(cmd, out, err))


def git_call(cmd):
p = subprocess.Popen(
shlex.split('git %s' % cmd),
shlex.split('git {0}'.format(cmd)),
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
# Python 2/3 compatibility.
Expand Down Expand Up @@ -76,10 +76,8 @@ def real_case(fp):
found = True
break
if not found:
# TODO(sperezde): fix this hack
# raise Exception("Invalid file %s: the file doesn't exist" % fp)
# Temp hack until I figure out how to deal with filenames with special
# characters.
# TODO(sperezde): fix this hack (deal with filenames with special
# characters).
return fp
return os.path.join(*ret)

Expand Down Expand Up @@ -116,17 +114,15 @@ def remove_dups(list, key):
list: the list to read from.
key: a function that receives an element from list and returns its key.
Returns:
a new list without duplicates.
Yields:
unique elements of the given list
"""
keys = set()
ret = []
for a in list:
k_a = key(a)
if k_a not in keys:
keys.add(k_a)
ret.append(a)
return ret
yield a


def get_all_fps_under_cwd():
Expand All @@ -140,3 +136,11 @@ def get_all_fps_under_cwd():
dirnames.remove('.git')
for fp in filenames:
yield os.path.relpath(os.path.join(dirpath, fp))


def items(dic):
"""Py 2/3 compatible way of getting the items of a dictionary."""
try:
return dic.iteritems()
except AttributeError:
return iter(dic.items())
2 changes: 1 addition & 1 deletion gitpylib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@


def get(var):
ok, out, _ = common.git_call('config %s' % var)
ok, out, _ = common.git_call('config {0}'.format(var))
return out.strip() if ok else None
25 changes: 13 additions & 12 deletions gitpylib/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def stage(fp):

fp = common.real_case(fp)

common.safe_git_call('add "%s"' % fp)
common.safe_git_call('add "{0}"'.format(fp))
return SUCCESS


Expand All @@ -58,7 +58,7 @@ def unstage(fp):
# http://comments.gmane.org/gmane.comp.version-control.git/211242.
# So, we need to ignore the return code (unfortunately) and hope that it
# works.
common.git_call('reset HEAD "%s"' % fp)
common.git_call('reset HEAD "{0}"'.format(fp))
return SUCCESS


Expand All @@ -75,12 +75,12 @@ def show(fp, cp):
"""
fp = common.real_case(fp)

ok, out, _ = common.git_call('show %s:"%s"' % (cp, fp))
ok, out, _ = common.git_call('show {0}:"{1}"'.format(cp, fp))

if not ok:
return (FILE_NOT_FOUND_AT_CP, None)
return FILE_NOT_FOUND_AT_CP, None

return (SUCCESS, out)
return SUCCESS, out


def assume_unchanged(fp):
Expand All @@ -94,7 +94,7 @@ def assume_unchanged(fp):
"""
fp = common.real_case(fp)

common.safe_git_call('update-index --assume-unchanged "%s"' % fp)
common.safe_git_call('update-index --assume-unchanged "{0}"'.format(fp))
return SUCCESS


Expand All @@ -109,7 +109,7 @@ def not_assume_unchanged(fp):
"""
fp = common.real_case(fp)

common.safe_git_call('update-index --no-assume-unchanged "%s"' % fp)
common.safe_git_call('update-index --no-assume-unchanged "{0}"'.format(fp))
return SUCCESS


Expand Down Expand Up @@ -137,14 +137,15 @@ def diff(fp, staged=False):
fp = common.real_case(fp)

st = '--cached' if staged else ''
out, _ = common.safe_git_call('diff %s -- "%s"' % (st, fp))
out, _ = common.safe_git_call('diff {0} -- "{1}"'.format(st, fp))
if not out:
return ([], 0, 0, 0, None)
stats_out, _ = common.safe_git_call('diff %s --numstat -- "%s"' % (st, fp))
return [], 0, 0, 0, None
stats_out, _ = common.safe_git_call(
'diff {0} --numstat -- "{1}"'.format(st, fp))
header, body = _split_diff(out.splitlines())
line, padding = _process_diff_output(body)
additions, removals = _process_diff_stats_output(stats_out)
return (line, padding, additions, removals, header)
return line, padding, additions, removals, header


# Private functions.
Expand Down Expand Up @@ -206,4 +207,4 @@ def _process_diff_output(diff_out):
def _process_diff_stats_output(diff_stats_out):
# format is additions tab removals.
m = re.match(r'([0-9]+)\t([0-9]+)', diff_stats_out)
return (int(m.group(1)), int(m.group(2)))
return int(m.group(1)), int(m.group(2))
2 changes: 1 addition & 1 deletion gitpylib/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from . import file as git_file


# TODO: add more info like parents, tree, committer, etc.
Commit = collections.namedtuple(
'Commit', ['id', 'author', 'msg', 'diffs'])

Expand All @@ -19,6 +18,7 @@
CommitDiff = collections.namedtuple(
'CommitDiff', ['fp_before', 'fp_after', 'diff'])


def log(include_diffs=False):
log_fmt = r'[[%H] [%an] [%ae] [%aD] [%ar]]%n%B'
out, _ = common.safe_git_call(
Expand Down
20 changes: 10 additions & 10 deletions gitpylib/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ def add(remote_name, remote_url):
"""
if _show(remote_url)[0] == REMOTE_UNREACHABLE:
return REMOTE_UNREACHABLE
common.safe_git_call('remote add %s %s' % (remote_name, remote_url))
common.safe_git_call('fetch %s' % remote_name)
common.safe_git_call('remote add {0} {1}'.format(remote_name, remote_url))
common.safe_git_call('fetch {0}'.format(remote_name))
return SUCCESS


Expand All @@ -46,7 +46,7 @@ def show(remote_name):
REMOTE_UNREACHABLE and out is the output of the show command on success.
"""
if remote_name not in show_all():
return (REMOTE_NOT_FOUND, None)
return REMOTE_NOT_FOUND, None
return _show(remote_name)


Expand Down Expand Up @@ -81,15 +81,15 @@ def show_all_v():


def rm(remote_name):
common.safe_git_call('remote rm %s' % remote_name)
common.safe_git_call('remote rm {0}'.format(remote_name))


def head_exist(remote_name, head):
ok, out, _ = common.git_call(
'ls-remote --heads %s %s' % (remote_name, head))
'ls-remote --heads {0} {1}'.format(remote_name, head))
if not ok:
return (False, REMOTE_UNREACHABLE)
return (len(out) > 0, REMOTE_BRANCH_NOT_FOUND)
return False, REMOTE_UNREACHABLE
return len(out) > 0, REMOTE_BRANCH_NOT_FOUND


def branches(remote_name):
Expand All @@ -108,9 +108,9 @@ def branches(remote_name):


def _show(remote):
ok, out, err = common.git_call('remote show %s' % remote)
ok, out, err = common.git_call('remote show {0}'.format(remote))
if not ok:
if 'fatal: Could not read from remote repository' in err:
return (REMOTE_UNREACHABLE, None)
return REMOTE_UNREACHABLE, None
raise common.UnexpectedOutputError('remote', out, err=err)
return (SUCCESS, out)
return SUCCESS, out
2 changes: 1 addition & 1 deletion gitpylib/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

def clone(repo):
"""Returns True if the clone succeeded, False if otherwise."""
return common.git_call('clone %s .' % repo)[0]
return common.git_call('clone {0} .'.format(repo))[0]


def init():
Expand Down
Loading

0 comments on commit 848a3d3

Please sign in to comment.