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
Release 0.6:

  * Unset upstream support.
  * Better wrapping of git log.
  * Apply patch suppport.
  * Return number of additions and deletions in diff.
  * Bug fixes and code cleanups.
  • Loading branch information
spderosso committed Mar 25, 2014
2 parents 880d530 + 291fa69 commit fa418b1
Show file tree
Hide file tree
Showing 14 changed files with 197 additions and 99 deletions.
10 changes: 10 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ Gitpylib's Release Notes
========================


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

* Unset upstream support.
* Better wrapping of `git log`.
* Apply patch suppport.
* Return # of additions and deletions in diff.
* Bug fixes and code cleanups.


3rd Feb 2014 - 0.5
------------------

Expand Down
10 changes: 10 additions & 0 deletions gitpylib/apply.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# gitpylib - a Python library for Git.
# Licensed under GNU GPL v2.


from . import common


def on_index(patch_file):
ok, _, _ = common.git_call('apply --cached {0}'.format(patch_file))
return ok
20 changes: 14 additions & 6 deletions gitpylib/branch.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# gitpylib - a Python library for Git.
# Copyright (c) 2013 Santiago Perez De Rosso.
# Licensed under GNU GPL, version 2.
# Licensed under GNU GPL v2.

"""Module for dealing with Git branches."""

Expand Down Expand Up @@ -118,16 +117,25 @@ def set_upstream(branch, upstream_branch):
branch: the branch to set an upstream branch.
upstream_branch: the upstream branch.
"""
ok, _, err = common.git_call(
ok, _, _ = common.git_call(
'branch --set-upstream %s %s' % (branch, upstream_branch))

if not ok:
if 'Not a valid object name' in err:
return UNFETCHED_OBJECT
return UNFETCHED_OBJECT

return SUCCESS


def unset_upstream(branch):
"""Unsets the upstream branch to branch.
Args:
branch: the branch to unset its upstream.
"""
common.git_call('branch --unset-upstream %s' % branch)
return SUCCESS


def _parse_output(out):
"""Parses branch list output.
Expand All @@ -148,7 +156,7 @@ def _parse_output(out):
pattern = r'([\*| ]) ([^\s]+)[ ]+\w+ (.+)'
result = re.match(pattern, out)
if not result:
raise Exception('Unexpected output %s' % out)
raise common.UnexpectedOutputError('branch', out)

tracks = None
if result.group(3)[0] == '[':
Expand Down
23 changes: 19 additions & 4 deletions gitpylib/common.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# gitpylib - a Python library for Git.
# Copyright (c) 2013 Santiago Perez De Rosso.
# Licensed under GNU GPL, version 2.
# Licensed under GNU GPL v2.

"""Common methods used accross the gitpylib."""


import os
import shlex
import subprocess
import sys

Expand All @@ -18,6 +18,21 @@
FS_CASE_SENSITIVE = not os.path.exists(f_tmp.name.upper())


class UnexpectedOutputError(Exception):

def __init__(self, cmd, out, err=None):
super(UnexpectedOutputError, self).__init__()
self.cmd = cmd
self.out = out
self.err = err

def __str__(self):
err = 'err was:\n{0}\n' if self.err else ''
return (
'Unexpected output from cmd {0}, out was:\n{1}\n{2}'.format(
self.cmd, self.out, err))


def safe_git_call(cmd):
ok, out, err = git_call(cmd)
if ok:
Expand All @@ -27,8 +42,8 @@ def safe_git_call(cmd):

def git_call(cmd):
p = subprocess.Popen(
'git %s' % cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
shell=True)
shlex.split('git %s' % cmd),
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
# Python 2/3 compatibility.
if sys.version > '3':
Expand Down
3 changes: 1 addition & 2 deletions gitpylib/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# gitpylib - a Python library for Git.
# Copyright (c) 2013 Santiago Perez De Rosso.
# Licensed under GNU GPL, version 2.
# Licensed under GNU GPL v2.

"""Module for dealing with Git config."""

Expand Down
79 changes: 36 additions & 43 deletions gitpylib/file.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# gitpylib - a Python library for Git.
# Copyright (c) 2013 Santiago Perez De Rosso.
# Licensed under GNU GPL, version 2.
# Licensed under GNU GPL v2.

"""Module for dealing with Git files."""

Expand Down Expand Up @@ -114,66 +113,54 @@ def not_assume_unchanged(fp):
return SUCCESS


def diff(fp):
def diff(fp, staged=False):
"""Compute the diff of the given file with its last committed version.
Args:
fp: the path of the file to diff (e.g., 'paper.tex').
staged: True if a staged diff should be done.
Returns:
the output of the diff command.
"""
fp = common.real_case(fp)

out, _ = common.safe_git_call('diff -- "%s"' % fp)
return _process_diff_output(_strip_diff_header(out.splitlines()))


def staged_diff(fp):
"""Compute the diff of staged version vs last committed version.
Args:
fp: the path of the file to diff (e.g., 'paper.tex').
Returns:
the output of the diff command.
a 4-tuple of:
- a list of namedtuples with fields 'line', 'status', 'old_line_number'
and 'new_line_number' where 'status' is one of DIFF_INFO, DIFF_SAME,
DIFF_ADDED or DIFF_MINUS and 'old_line_number', 'new_line_number'
correspond to the line's old line number and new line number respectively.
(Note that, for example, if the line is DIFF_ADDED, then 'old_line_number'
is None since that line is not present in the old file).
- max_line_digits: the maximum amount of line digits found while parsing
the git diff output, this is useful for padding.
- number of lines added.
- number of lines removed.
- header (the diff header as a list of lines).
"""
fp = common.real_case(fp)

out, _ = common.safe_git_call('diff --cached -- "%s"' % fp)
return _process_diff_output(_strip_diff_header(out.splitlines()))
st = '--cached' if staged else ''
out, _ = common.safe_git_call('diff %s -- "%s"' % (st, fp))
if not out:
return ([], 0, 0, 0, None)
stats_out, _ = common.safe_git_call('diff %s --numstat -- "%s"' % (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)


# Private functions.


def _strip_diff_header(diff_out):
"""Removes the diff header lines."""
def _split_diff(diff_out):
"""Splits the diff output into the diff header and body."""
first_non_header_line = 0
for line in diff_out:
if line.startswith('@@'):
break
first_non_header_line += 1
return diff_out[first_non_header_line:]
return diff_out[:first_non_header_line], diff_out[first_non_header_line:]


def _process_diff_output(diff_out):
"""Process the git diff output.
Args:
diff_out: a list of lines output by the git diff command.
Returns:
a 2-tuple of:
- a list of namedtuples with fields 'line', 'status', 'old_line_number'
and 'new_line_number' where 'status' is one of DIFF_INFO, DIFF_SAME,
DIFF_ADDED or DIFF_MINUS and 'old_line_number', 'new_line_number'
correspond to the line's old line number and new line number respectively.
(Note that, for example, if the line is DIFF_ADDED, then 'old_line_number'
is None since that line is not present in the old file).
- max_line_digits: return the maximum amount of line digits found while
parsing the git diff output, this is useful for padding.
"""
MIN_LINE_PADDING = 8
LineData = collections.namedtuple(
'LineData',
Expand All @@ -184,9 +171,9 @@ def _process_diff_output(diff_out):
old_line_number = 1
new_line_number = 1

# @@ -(start of old),(length of old) +(start of new),(length of new) @@
new_hunk_regex = r'^@@ -([0-9]+)[,]?([0-9]*) \+([0-9]+)[,]?([0-9]*) @@'
for line in diff_out:
# @@ -(start of old),(length of old) +(start of new),(length of new) @@
new_hunk_regex = r'^@@ -([0-9]+)[,]?([0-9]*) \+([0-9]+)[,]?([0-9]*) @@'
new_hunk_info = re.search(new_hunk_regex, line)
if new_hunk_info:
get_info_or_zero = lambda g: 0 if g == '' else int(g)
Expand All @@ -201,7 +188,7 @@ def _process_diff_output(diff_out):
max_line_digits]) # start + length of each diff.
elif line.startswith(' '):
resulting.append(
LineData(line, DIFF_SAME, old_line_number, new_line_number))
LineData(line, DIFF_SAME, old_line_number, new_line_number))
old_line_number += 1
new_line_number += 1
elif line.startswith('-'):
Expand All @@ -214,3 +201,9 @@ def _process_diff_output(diff_out):
max_line_digits = len(str(max_line_digits)) # digits = len(string of number).
max_line_digits = max(MIN_LINE_PADDING, max_line_digits + 1)
return resulting, max_line_digits


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)))
3 changes: 1 addition & 2 deletions gitpylib/hook.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# gitpylib - a Python library for Git.
# Copyright (c) 2013 Santiago Perez De Rosso.
# Licensed under GNU GPL, version 2.
# Licensed under GNU GPL v2.

"""Hook module for running Git hooks."""

Expand Down
83 changes: 74 additions & 9 deletions gitpylib/log.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,81 @@
# gitpylib - a Python library for Git.
# Copyright (c) 2013 Santiago Perez De Rosso.
# Licensed under GNU GPL, version 2.
# Licensed under GNU GPL v2.


import subprocess
import collections
import re

from . import common
from . import file as git_file

def log():
# The pipe to less shouldn't be here. TODO: fix.
subprocess.call('git log | less', shell=True)

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

def log_p():
# The pipe to less shouldn't be here. TODO: fix.
subprocess.call('git log -p | less', shell=True)
CommitAuthor = collections.namedtuple(
'CommitAuthor', ['name', 'email', 'date', 'date_relative'])

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(
'log --format=format:"{0}" {1}'.format(
log_fmt, '-p' if include_diffs else ''))
return _parse_log_output(out)


def _parse_log_output(out):
def _create_ci(m, msg, diffs):
processed_diffs = []
for diff in diffs:
fp_before, fp_after = re.match(
'diff --git a/(.*) b/(.*)', diff[0]).group(1, 2)
processed_diffs.append(
CommitDiff(
fp_before, fp_after,
git_file._process_diff_output(git_file._split_diff(diff)[1])))
return Commit(
m.group(1), CommitAuthor(*m.group(2, 3, 4, 5)), '\n'.join(msg),
processed_diffs)

if not out:
return []

pattern = re.compile(r'\[\[(.*)\] \[(.*)\] \[(.*)\] \[(.*)\] \[(.*)\]\]')
out_it = iter(out.splitlines())
m = None
msg = None
diffs = []
curr_diff = None
ret = []
try:
line = next(out_it)
while True:
m = re.match(pattern, line)
if not m:
raise common.UnexpectedOutputError('log', line)
line = next(out_it)
msg = []
while not line.startswith('diff --git') and not line.startswith('[['):
msg.append(line)
line = next(out_it)
diffs = []
curr_diff = []
while not line.startswith('[['):
curr_diff.append(line)
line = next(out_it)
while not line.startswith('diff --git') and not line.startswith('[['):
curr_diff.append(line)
line = next(out_it)
diffs.append(curr_diff)
curr_diff = []

ret.append(_create_ci(m, msg, diffs))
except StopIteration:
if curr_diff:
diffs.append(curr_diff)
ret.append(_create_ci(m, msg, diffs))
return ret
8 changes: 3 additions & 5 deletions gitpylib/remote.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# gitpylib - a Python library for Git.
# Copyright (c) 2013 Santiago Perez De Rosso.
# Licensed under GNU GPL, version 2.
# Licensed under GNU GPL v2.

"""Module for dealing with Git remotes."""

Expand Down Expand Up @@ -70,7 +69,7 @@ def show_all_v():
for r in out.splitlines():
result = re.match(pattern, r)
if not result:
raise Exception('Unexpected output "%s"' % r)
raise common.UnexpectedOutputError('remote', r)
remote_name = result.group(1)
url = result.group(2)
url_type = result.group(3)
Expand Down Expand Up @@ -113,6 +112,5 @@ def _show(remote):
if not ok:
if 'fatal: Could not read from remote repository' in err:
return (REMOTE_UNREACHABLE, None)
else:
raise Exception('Unexpected output %s, err %s' % (out, err))
raise common.UnexpectedOutputError('remote', out, err=err)
return (SUCCESS, out)
3 changes: 1 addition & 2 deletions gitpylib/repo.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# gitpylib - a Python library for Git.
# Copyright (c) 2013 Santiago Perez De Rosso.
# Licensed under GNU GPL, version 2.
# Licensed under GNU GPL v2.


from . import common
Expand Down
Loading

0 comments on commit fa418b1

Please sign in to comment.