python 3 migration

This commit is contained in:
manuel83
2019-08-19 22:51:08 +02:00
parent 63f9f5468b
commit 996bc54768
1363 changed files with 351010 additions and 62 deletions
@@ -0,0 +1,86 @@
# __init__.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
# flake8: noqa
#@PydevCodeAnalysisIgnore
import inspect
import os
import sys
import os.path as osp
__version__ = '3.0.1'
#{ Initialization
def _init_externals():
"""Initialize external projects by putting them into the path"""
if __version__ == '3.0.1':
sys.path.insert(0, osp.join(osp.dirname(__file__), 'ext', 'gitdb'))
try:
import gitdb
except ImportError:
raise ImportError("'gitdb' could not be found in your PYTHONPATH")
# END verify import
#} END initialization
#################
_init_externals()
#################
#{ Imports
from git.exc import * # @NoMove @IgnorePep8
try:
from git.config import GitConfigParser # @NoMove @IgnorePep8
from git.objects import * # @NoMove @IgnorePep8
from git.refs import * # @NoMove @IgnorePep8
from git.diff import * # @NoMove @IgnorePep8
from git.db import * # @NoMove @IgnorePep8
from git.cmd import Git # @NoMove @IgnorePep8
from git.repo import Repo # @NoMove @IgnorePep8
from git.remote import * # @NoMove @IgnorePep8
from git.index import * # @NoMove @IgnorePep8
from git.util import ( # @NoMove @IgnorePep8
LockFile,
BlockingLockFile,
Stats,
Actor,
rmtree,
)
except GitError as exc:
raise ImportError('%s: %s' % (exc.__class__.__name__, exc))
#} END imports
__all__ = [name for name, obj in locals().items()
if not (name.startswith('_') or inspect.ismodule(obj))]
#{ Initialize git executable path
GIT_OK = None
def refresh(path=None):
"""Convenience method for setting the git executable path."""
global GIT_OK
GIT_OK = False
if not Git.refresh(path=path):
return
if not FetchInfo.refresh():
return
GIT_OK = True
#} END initialize git executable path
#################
try:
refresh()
except Exception as exc:
raise ImportError('Failed to initialize: {0}'.format(exc))
#################
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,316 @@
# -*- coding: utf-8 -*-
# config.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
"""utilities to help provide compatibility with python 3"""
# flake8: noqa
import locale
import os
import sys
import codecs
from gitdb.utils.compat import (
xrange,
MAXSIZE, # @UnusedImport
izip, # @UnusedImport
)
from gitdb.utils.encoding import (
string_types, # @UnusedImport
text_type, # @UnusedImport
force_bytes, # @UnusedImport
force_text # @UnusedImport
)
PY3 = sys.version_info[0] >= 3
is_win = (os.name == 'nt')
is_posix = (os.name == 'posix')
is_darwin = (os.name == 'darwin')
if hasattr(sys, 'getfilesystemencoding'):
defenc = sys.getfilesystemencoding()
if defenc is None:
defenc = sys.getdefaultencoding()
if PY3:
import io
FileType = io.IOBase
def byte_ord(b):
return b
def bchr(n):
return bytes([n])
def mviter(d):
return d.values()
range = xrange # @ReservedAssignment
unicode = str
binary_type = bytes
else:
FileType = file # @UndefinedVariable on PY3
# usually, this is just ascii, which might not enough for our encoding needs
# Unless it's set specifically, we override it to be utf-8
if defenc == 'ascii':
defenc = 'utf-8'
byte_ord = ord
bchr = chr
unicode = unicode
binary_type = str
range = xrange # @ReservedAssignment
def mviter(d):
return d.itervalues()
def safe_decode(s):
"""Safely decodes a binary string to unicode"""
if isinstance(s, unicode):
return s
elif isinstance(s, bytes):
return s.decode(defenc, 'surrogateescape')
elif s is not None:
raise TypeError('Expected bytes or text, but got %r' % (s,))
def safe_encode(s):
"""Safely decodes a binary string to unicode"""
if isinstance(s, unicode):
return s.encode(defenc)
elif isinstance(s, bytes):
return s
elif s is not None:
raise TypeError('Expected bytes or text, but got %r' % (s,))
def win_encode(s):
"""Encode unicodes for process arguments on Windows."""
if isinstance(s, unicode):
return s.encode(locale.getpreferredencoding(False))
elif isinstance(s, bytes):
return s
elif s is not None:
raise TypeError('Expected bytes or text, but got %r' % (s,))
def with_metaclass(meta, *bases):
"""copied from https://github.com/Byron/bcore/blob/master/src/python/butility/future.py#L15"""
class metaclass(meta):
__call__ = type.__call__
__init__ = type.__init__
def __new__(cls, name, nbases, d):
if nbases is None:
return type.__new__(cls, name, (), d)
# There may be clients who rely on this attribute to be set to a reasonable value, which is why
# we set the __metaclass__ attribute explicitly
if not PY3 and '___metaclass__' not in d:
d['__metaclass__'] = meta
return meta(name, bases, d)
return metaclass(meta.__name__ + 'Helper', None, {})
## From https://docs.python.org/3.3/howto/pyporting.html
class UnicodeMixin(object):
"""Mixin class to handle defining the proper __str__/__unicode__
methods in Python 2 or 3."""
if PY3:
def __str__(self):
return self.__unicode__()
else: # Python 2
def __str__(self):
return self.__unicode__().encode(defenc)
"""
This is Victor Stinner's pure-Python implementation of PEP 383: the "surrogateescape" error
handler of Python 3.
Source: misc/python/surrogateescape.py in https://bitbucket.org/haypo/misc
"""
# This code is released under the Python license and the BSD 2-clause license
FS_ERRORS = 'surrogateescape'
# # -- Python 2/3 compatibility -------------------------------------
# FS_ERRORS = 'my_surrogateescape'
def u(text):
if PY3:
return text
else:
return text.decode('unicode_escape')
def b(data):
if PY3:
return data.encode('latin1')
else:
return data
if PY3:
_unichr = chr
bytes_chr = lambda code: bytes((code,))
else:
_unichr = unichr
bytes_chr = chr
def surrogateescape_handler(exc):
"""
Pure Python implementation of the PEP 383: the "surrogateescape" error
handler of Python 3. Undecodable bytes will be replaced by a Unicode
character U+DCxx on decoding, and these are translated into the
original bytes on encoding.
"""
mystring = exc.object[exc.start:exc.end]
try:
if isinstance(exc, UnicodeDecodeError):
# mystring is a byte-string in this case
decoded = replace_surrogate_decode(mystring)
elif isinstance(exc, UnicodeEncodeError):
# In the case of u'\udcc3'.encode('ascii',
# 'this_surrogateescape_handler'), both Python 2.x and 3.x raise an
# exception anyway after this function is called, even though I think
# it's doing what it should. It seems that the strict encoder is called
# to encode the unicode string that this function returns ...
decoded = replace_surrogate_encode(mystring, exc)
else:
raise exc
except NotASurrogateError:
raise exc
return (decoded, exc.end)
class NotASurrogateError(Exception):
pass
def replace_surrogate_encode(mystring, exc):
"""
Returns a (unicode) string, not the more logical bytes, because the codecs
register_error functionality expects this.
"""
decoded = []
for ch in mystring:
# if PY3:
# code = ch
# else:
code = ord(ch)
# The following magic comes from Py3.3's Python/codecs.c file:
if not 0xD800 <= code <= 0xDCFF:
# Not a surrogate. Fail with the original exception.
raise exc
# mybytes = [0xe0 | (code >> 12),
# 0x80 | ((code >> 6) & 0x3f),
# 0x80 | (code & 0x3f)]
# Is this a good idea?
if 0xDC00 <= code <= 0xDC7F:
decoded.append(_unichr(code - 0xDC00))
elif code <= 0xDCFF:
decoded.append(_unichr(code - 0xDC00))
else:
raise NotASurrogateError
return str().join(decoded)
def replace_surrogate_decode(mybytes):
"""
Returns a (unicode) string
"""
decoded = []
for ch in mybytes:
# We may be parsing newbytes (in which case ch is an int) or a native
# str on Py2
if isinstance(ch, int):
code = ch
else:
code = ord(ch)
if 0x80 <= code <= 0xFF:
decoded.append(_unichr(0xDC00 + code))
elif code <= 0x7F:
decoded.append(_unichr(code))
else:
# # It may be a bad byte
# # Try swallowing it.
# continue
# print("RAISE!")
raise NotASurrogateError
return str().join(decoded)
def encodefilename(fn):
if FS_ENCODING == 'ascii':
# ASCII encoder of Python 2 expects that the error handler returns a
# Unicode string encodable to ASCII, whereas our surrogateescape error
# handler has to return bytes in 0x80-0xFF range.
encoded = []
for index, ch in enumerate(fn):
code = ord(ch)
if code < 128:
ch = bytes_chr(code)
elif 0xDC80 <= code <= 0xDCFF:
ch = bytes_chr(code - 0xDC00)
else:
raise UnicodeEncodeError(FS_ENCODING,
fn, index, index+1,
'ordinal not in range(128)')
encoded.append(ch)
return bytes().join(encoded)
elif FS_ENCODING == 'utf-8':
# UTF-8 encoder of Python 2 encodes surrogates, so U+DC80-U+DCFF
# doesn't go through our error handler
encoded = []
for index, ch in enumerate(fn):
code = ord(ch)
if 0xD800 <= code <= 0xDFFF:
if 0xDC80 <= code <= 0xDCFF:
ch = bytes_chr(code - 0xDC00)
encoded.append(ch)
else:
raise UnicodeEncodeError(
FS_ENCODING,
fn, index, index+1, 'surrogates not allowed')
else:
ch_utf8 = ch.encode('utf-8')
encoded.append(ch_utf8)
return bytes().join(encoded)
else:
return fn.encode(FS_ENCODING, FS_ERRORS)
def decodefilename(fn):
return fn.decode(FS_ENCODING, FS_ERRORS)
FS_ENCODING = 'ascii'; fn = b('[abc\xff]'); encoded = u('[abc\udcff]')
# FS_ENCODING = 'cp932'; fn = b('[abc\x81\x00]'); encoded = u('[abc\udc81\x00]')
# FS_ENCODING = 'UTF-8'; fn = b('[abc\xff]'); encoded = u('[abc\udcff]')
# normalize the filesystem encoding name.
# For example, we expect "utf-8", not "UTF8".
FS_ENCODING = codecs.lookup(FS_ENCODING).name
def register_surrogateescape():
"""
Registers the surrogateescape error handler on Python 2 (only)
"""
if PY3:
return
try:
codecs.lookup_error(FS_ERRORS)
except LookupError:
codecs.register_error(FS_ERRORS, surrogateescape_handler)
try:
b"100644 \x9f\0aaa".decode(defenc, "surrogateescape")
except Exception:
register_surrogateescape()
@@ -0,0 +1,710 @@
# config.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Module containing module parser implementation able to properly read and write
configuration files"""
import abc
from functools import wraps
import inspect
import logging
import os
import re
from collections import OrderedDict
from git.compat import (
string_types,
FileType,
defenc,
force_text,
with_metaclass,
PY3
)
from git.util import LockFile
import os.path as osp
try:
import ConfigParser as cp
except ImportError:
# PY3
import configparser as cp
__all__ = ('GitConfigParser', 'SectionConstraint')
log = logging.getLogger('git.config')
log.addHandler(logging.NullHandler())
class MetaParserBuilder(abc.ABCMeta):
"""Utlity class wrapping base-class methods into decorators that assure read-only properties"""
def __new__(cls, name, bases, clsdict):
"""
Equip all base-class methods with a needs_values decorator, and all non-const methods
with a set_dirty_and_flush_changes decorator in addition to that."""
kmm = '_mutating_methods_'
if kmm in clsdict:
mutating_methods = clsdict[kmm]
for base in bases:
methods = (t for t in inspect.getmembers(base, inspect.isroutine) if not t[0].startswith("_"))
for name, method in methods:
if name in clsdict:
continue
method_with_values = needs_values(method)
if name in mutating_methods:
method_with_values = set_dirty_and_flush_changes(method_with_values)
# END mutating methods handling
clsdict[name] = method_with_values
# END for each name/method pair
# END for each base
# END if mutating methods configuration is set
new_type = super(MetaParserBuilder, cls).__new__(cls, name, bases, clsdict)
return new_type
def needs_values(func):
"""Returns method assuring we read values (on demand) before we try to access them"""
@wraps(func)
def assure_data_present(self, *args, **kwargs):
self.read()
return func(self, *args, **kwargs)
# END wrapper method
return assure_data_present
def set_dirty_and_flush_changes(non_const_func):
"""Return method that checks whether given non constant function may be called.
If so, the instance will be set dirty.
Additionally, we flush the changes right to disk"""
def flush_changes(self, *args, **kwargs):
rval = non_const_func(self, *args, **kwargs)
self._dirty = True
self.write()
return rval
# END wrapper method
flush_changes.__name__ = non_const_func.__name__
return flush_changes
class SectionConstraint(object):
"""Constrains a ConfigParser to only option commands which are constrained to
always use the section we have been initialized with.
It supports all ConfigParser methods that operate on an option.
:note:
If used as a context manager, will release the wrapped ConfigParser."""
__slots__ = ("_config", "_section_name")
_valid_attrs_ = ("get_value", "set_value", "get", "set", "getint", "getfloat", "getboolean", "has_option",
"remove_section", "remove_option", "options")
def __init__(self, config, section):
self._config = config
self._section_name = section
def __del__(self):
# Yes, for some reason, we have to call it explicitly for it to work in PY3 !
# Apparently __del__ doesn't get call anymore if refcount becomes 0
# Ridiculous ... .
self._config.release()
def __getattr__(self, attr):
if attr in self._valid_attrs_:
return lambda *args, **kwargs: self._call_config(attr, *args, **kwargs)
return super(SectionConstraint, self).__getattribute__(attr)
def _call_config(self, method, *args, **kwargs):
"""Call the configuration at the given method which must take a section name
as first argument"""
return getattr(self._config, method)(self._section_name, *args, **kwargs)
@property
def config(self):
"""return: Configparser instance we constrain"""
return self._config
def release(self):
"""Equivalent to GitConfigParser.release(), which is called on our underlying parser instance"""
return self._config.release()
def __enter__(self):
self._config.__enter__()
return self
def __exit__(self, exception_type, exception_value, traceback):
self._config.__exit__(exception_type, exception_value, traceback)
class _OMD(OrderedDict):
"""Ordered multi-dict."""
def __setitem__(self, key, value):
super(_OMD, self).__setitem__(key, [value])
def add(self, key, value):
if key not in self:
super(_OMD, self).__setitem__(key, [value])
return
super(_OMD, self).__getitem__(key).append(value)
def setall(self, key, values):
super(_OMD, self).__setitem__(key, values)
def __getitem__(self, key):
return super(_OMD, self).__getitem__(key)[-1]
def getlast(self, key):
return super(_OMD, self).__getitem__(key)[-1]
def setlast(self, key, value):
if key not in self:
super(_OMD, self).__setitem__(key, [value])
return
prior = super(_OMD, self).__getitem__(key)
prior[-1] = value
def get(self, key, default=None):
return super(_OMD, self).get(key, [default])[-1]
def getall(self, key):
return super(_OMD, self).__getitem__(key)
def items(self):
"""List of (key, last value for key)."""
return [(k, self[k]) for k in self]
def items_all(self):
"""List of (key, list of values for key)."""
return [(k, self.getall(k)) for k in self]
class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, object)):
"""Implements specifics required to read git style configuration files.
This variation behaves much like the git.config command such that the configuration
will be read on demand based on the filepath given during initialization.
The changes will automatically be written once the instance goes out of scope, but
can be triggered manually as well.
The configuration file will be locked if you intend to change values preventing other
instances to write concurrently.
:note:
The config is case-sensitive even when queried, hence section and option names
must match perfectly.
If used as a context manager, will release the locked file."""
#{ Configuration
# The lock type determines the type of lock to use in new configuration readers.
# They must be compatible to the LockFile interface.
# A suitable alternative would be the BlockingLockFile
t_lock = LockFile
re_comment = re.compile(r'^\s*[#;]')
#} END configuration
optvalueonly_source = r'\s*(?P<option>[^:=\s][^:=]*)'
OPTVALUEONLY = re.compile(optvalueonly_source)
OPTCRE = re.compile(optvalueonly_source + r'\s*(?P<vi>[:=])\s*' + r'(?P<value>.*)$')
del optvalueonly_source
# list of RawConfigParser methods able to change the instance
_mutating_methods_ = ("add_section", "remove_section", "remove_option", "set")
def __init__(self, file_or_files, read_only=True, merge_includes=True):
"""Initialize a configuration reader to read the given file_or_files and to
possibly allow changes to it by setting read_only False
:param file_or_files:
A single file path or file objects or multiple of these
:param read_only:
If True, the ConfigParser may only read the data , but not change it.
If False, only a single file path or file object may be given. We will write back the changes
when they happen, or when the ConfigParser is released. This will not happen if other
configuration files have been included
:param merge_includes: if True, we will read files mentioned in [include] sections and merge their
contents into ours. This makes it impossible to write back an individual configuration file.
Thus, if you want to modify a single configuration file, turn this off to leave the original
dataset unaltered when reading it."""
cp.RawConfigParser.__init__(self, dict_type=_OMD)
# Used in python 3, needs to stay in sync with sections for underlying implementation to work
if not hasattr(self, '_proxies'):
self._proxies = self._dict()
self._file_or_files = file_or_files
self._read_only = read_only
self._dirty = False
self._is_initialized = False
self._merge_includes = merge_includes
self._lock = None
self._acquire_lock()
def _acquire_lock(self):
if not self._read_only:
if not self._lock:
if isinstance(self._file_or_files, (tuple, list)):
raise ValueError(
"Write-ConfigParsers can operate on a single file only, multiple files have been passed")
# END single file check
file_or_files = self._file_or_files
if not isinstance(self._file_or_files, string_types):
file_or_files = self._file_or_files.name
# END get filename from handle/stream
# initialize lock base - we want to write
self._lock = self.t_lock(file_or_files)
# END lock check
self._lock._obtain_lock()
# END read-only check
def __del__(self):
"""Write pending changes if required and release locks"""
# NOTE: only consistent in PY2
self.release()
def __enter__(self):
self._acquire_lock()
return self
def __exit__(self, exception_type, exception_value, traceback):
self.release()
def release(self):
"""Flush changes and release the configuration write lock. This instance must not be used anymore afterwards.
In Python 3, it's required to explicitly release locks and flush changes, as __del__ is not called
deterministically anymore."""
# checking for the lock here makes sure we do not raise during write()
# in case an invalid parser was created who could not get a lock
if self.read_only or (self._lock and not self._lock._has_lock()):
return
try:
try:
self.write()
except IOError:
log.error("Exception during destruction of GitConfigParser", exc_info=True)
except ReferenceError:
# This happens in PY3 ... and usually means that some state cannot be written
# as the sections dict cannot be iterated
# Usually when shutting down the interpreter, don'y know how to fix this
pass
finally:
self._lock._release_lock()
def optionxform(self, optionstr):
"""Do not transform options in any way when writing"""
return optionstr
def _read(self, fp, fpname):
"""A direct copy of the py2.4 version of the super class's _read method
to assure it uses ordered dicts. Had to change one line to make it work.
Future versions have this fixed, but in fact its quite embarrassing for the
guys not to have done it right in the first place !
Removed big comments to make it more compact.
Made sure it ignores initial whitespace as git uses tabs"""
cursect = None # None, or a dictionary
optname = None
lineno = 0
is_multi_line = False
e = None # None, or an exception
def string_decode(v):
if v[-1] == '\\':
v = v[:-1]
# end cut trailing escapes to prevent decode error
if PY3:
return v.encode(defenc).decode('unicode_escape')
else:
return v.decode('string_escape')
# end
# end
while True:
# we assume to read binary !
line = fp.readline().decode(defenc)
if not line:
break
lineno = lineno + 1
# comment or blank line?
if line.strip() == '' or self.re_comment.match(line):
continue
if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
# no leading whitespace
continue
# is it a section header?
mo = self.SECTCRE.match(line.strip())
if not is_multi_line and mo:
sectname = mo.group('header').strip()
if sectname in self._sections:
cursect = self._sections[sectname]
elif sectname == cp.DEFAULTSECT:
cursect = self._defaults
else:
cursect = self._dict((('__name__', sectname),))
self._sections[sectname] = cursect
self._proxies[sectname] = None
# So sections can't start with a continuation line
optname = None
# no section header in the file?
elif cursect is None:
raise cp.MissingSectionHeaderError(fpname, lineno, line)
# an option line?
elif not is_multi_line:
mo = self.OPTCRE.match(line)
if mo:
# We might just have handled the last line, which could contain a quotation we want to remove
optname, vi, optval = mo.group('option', 'vi', 'value')
if vi in ('=', ':') and ';' in optval and not optval.strip().startswith('"'):
pos = optval.find(';')
if pos != -1 and optval[pos - 1].isspace():
optval = optval[:pos]
optval = optval.strip()
if optval == '""':
optval = ''
# end handle empty string
optname = self.optionxform(optname.rstrip())
if len(optval) > 1 and optval[0] == '"' and optval[-1] != '"':
is_multi_line = True
optval = string_decode(optval[1:])
# end handle multi-line
# preserves multiple values for duplicate optnames
cursect.add(optname, optval)
else:
# check if it's an option with no value - it's just ignored by git
if not self.OPTVALUEONLY.match(line):
if not e:
e = cp.ParsingError(fpname)
e.append(lineno, repr(line))
continue
else:
line = line.rstrip()
if line.endswith('"'):
is_multi_line = False
line = line[:-1]
# end handle quotations
optval = cursect.getlast(optname)
cursect.setlast(optname, optval + string_decode(line))
# END parse section or option
# END while reading
# if any parsing errors occurred, raise an exception
if e:
raise e
def _has_includes(self):
return self._merge_includes and self.has_section('include')
def read(self):
"""Reads the data stored in the files we have been initialized with. It will
ignore files that cannot be read, possibly leaving an empty configuration
:return: Nothing
:raise IOError: if a file cannot be handled"""
if self._is_initialized:
return
self._is_initialized = True
if not isinstance(self._file_or_files, (tuple, list)):
files_to_read = [self._file_or_files]
else:
files_to_read = list(self._file_or_files)
# end assure we have a copy of the paths to handle
seen = set(files_to_read)
num_read_include_files = 0
while files_to_read:
file_path = files_to_read.pop(0)
fp = file_path
file_ok = False
if hasattr(fp, "seek"):
self._read(fp, fp.name)
else:
# assume a path if it is not a file-object
try:
with open(file_path, 'rb') as fp:
file_ok = True
self._read(fp, fp.name)
except IOError:
continue
# Read includes and append those that we didn't handle yet
# We expect all paths to be normalized and absolute (and will assure that is the case)
if self._has_includes():
for _, include_path in self.items('include'):
if include_path.startswith('~'):
include_path = osp.expanduser(include_path)
if not osp.isabs(include_path):
if not file_ok:
continue
# end ignore relative paths if we don't know the configuration file path
assert osp.isabs(file_path), "Need absolute paths to be sure our cycle checks will work"
include_path = osp.join(osp.dirname(file_path), include_path)
# end make include path absolute
include_path = osp.normpath(include_path)
if include_path in seen or not os.access(include_path, os.R_OK):
continue
seen.add(include_path)
# insert included file to the top to be considered first
files_to_read.insert(0, include_path)
num_read_include_files += 1
# each include path in configuration file
# end handle includes
# END for each file object to read
# If there was no file included, we can safely write back (potentially) the configuration file
# without altering it's meaning
if num_read_include_files == 0:
self._merge_includes = False
# end
def _write(self, fp):
"""Write an .ini-format representation of the configuration state in
git compatible format"""
def write_section(name, section_dict):
fp.write(("[%s]\n" % name).encode(defenc))
for (key, values) in section_dict.items_all():
if key == "__name__":
continue
for v in values:
fp.write(("\t%s = %s\n" % (key, self._value_to_string(v).replace('\n', '\n\t'))).encode(defenc))
# END if key is not __name__
# END section writing
if self._defaults:
write_section(cp.DEFAULTSECT, self._defaults)
for name, value in self._sections.items():
write_section(name, value)
def items(self, section_name):
""":return: list((option, value), ...) pairs of all items in the given section"""
return [(k, v) for k, v in super(GitConfigParser, self).items(section_name) if k != '__name__']
def items_all(self, section_name):
""":return: list((option, [values...]), ...) pairs of all items in the given section"""
rv = _OMD(self._defaults)
for k, vs in self._sections[section_name].items_all():
if k == '__name__':
continue
if k in rv and rv.getall(k) == vs:
continue
for v in vs:
rv.add(k, v)
return rv.items_all()
@needs_values
def write(self):
"""Write changes to our file, if there are changes at all
:raise IOError: if this is a read-only writer instance or if we could not obtain
a file lock"""
self._assure_writable("write")
if not self._dirty:
return
if isinstance(self._file_or_files, (list, tuple)):
raise AssertionError("Cannot write back if there is not exactly a single file to write to, have %i files"
% len(self._file_or_files))
# end assert multiple files
if self._has_includes():
log.debug("Skipping write-back of configuration file as include files were merged in." +
"Set merge_includes=False to prevent this.")
return
# end
fp = self._file_or_files
# we have a physical file on disk, so get a lock
is_file_lock = isinstance(fp, string_types + (FileType, ))
if is_file_lock:
self._lock._obtain_lock()
if not hasattr(fp, "seek"):
with open(self._file_or_files, "wb") as fp:
self._write(fp)
else:
fp.seek(0)
# make sure we do not overwrite into an existing file
if hasattr(fp, 'truncate'):
fp.truncate()
self._write(fp)
def _assure_writable(self, method_name):
if self.read_only:
raise IOError("Cannot execute non-constant method %s.%s" % (self, method_name))
def add_section(self, section):
"""Assures added options will stay in order"""
return super(GitConfigParser, self).add_section(section)
@property
def read_only(self):
""":return: True if this instance may change the configuration file"""
return self._read_only
def get_value(self, section, option, default=None):
"""Get an option's value.
If multiple values are specified for this option in the section, the
last one specified is returned.
:param default:
If not None, the given default value will be returned in case
the option did not exist
:return: a properly typed value, either int, float or string
:raise TypeError: in case the value could not be understood
Otherwise the exceptions known to the ConfigParser will be raised."""
try:
valuestr = self.get(section, option)
except Exception:
if default is not None:
return default
raise
return self._string_to_value(valuestr)
def get_values(self, section, option, default=None):
"""Get an option's values.
If multiple values are specified for this option in the section, all are
returned.
:param default:
If not None, a list containing the given default value will be
returned in case the option did not exist
:return: a list of properly typed values, either int, float or string
:raise TypeError: in case the value could not be understood
Otherwise the exceptions known to the ConfigParser will be raised."""
try:
lst = self._sections[section].getall(option)
except Exception:
if default is not None:
return [default]
raise
return [self._string_to_value(valuestr) for valuestr in lst]
def _string_to_value(self, valuestr):
types = (int, float)
for numtype in types:
try:
val = numtype(valuestr)
# truncated value ?
if val != float(valuestr):
continue
return val
except (ValueError, TypeError):
continue
# END for each numeric type
# try boolean values as git uses them
vl = valuestr.lower()
if vl == 'false':
return False
if vl == 'true':
return True
if not isinstance(valuestr, string_types):
raise TypeError(
"Invalid value type: only int, long, float and str are allowed",
valuestr)
return valuestr
def _value_to_string(self, value):
if isinstance(value, (int, float, bool)):
return str(value)
return force_text(value)
@needs_values
@set_dirty_and_flush_changes
def set_value(self, section, option, value):
"""Sets the given option in section to the given value.
It will create the section if required, and will not throw as opposed to the default
ConfigParser 'set' method.
:param section: Name of the section in which the option resides or should reside
:param option: Name of the options whose value to set
:param value: Value to set the option to. It must be a string or convertible
to a string
:return: this instance"""
if not self.has_section(section):
self.add_section(section)
self.set(section, option, self._value_to_string(value))
return self
@needs_values
@set_dirty_and_flush_changes
def add_value(self, section, option, value):
"""Adds a value for the given option in section.
It will create the section if required, and will not throw as opposed to the default
ConfigParser 'set' method. The value becomes the new value of the option as returned
by 'get_value', and appends to the list of values returned by 'get_values`'.
:param section: Name of the section in which the option resides or should reside
:param option: Name of the option
:param value: Value to add to option. It must be a string or convertible
to a string
:return: this instance"""
if not self.has_section(section):
self.add_section(section)
self._sections[section].add(option, self._value_to_string(value))
return self
def rename_section(self, section, new_name):
"""rename the given section to new_name
:raise ValueError: if section doesn't exit
:raise ValueError: if a section with new_name does already exist
:return: this instance
"""
if not self.has_section(section):
raise ValueError("Source section '%s' doesn't exist" % section)
if self.has_section(new_name):
raise ValueError("Destination section '%s' already exists" % new_name)
super(GitConfigParser, self).add_section(new_name)
new_section = self._sections[new_name]
for k, vs in self.items_all(section):
new_section.setall(k, vs)
# end for each value to copy
# This call writes back the changes, which is why we don't have the respective decorator
self.remove_section(section)
return self
@@ -0,0 +1,60 @@
"""Module with our own gitdb implementation - it uses the git command"""
from git.util import bin_to_hex, hex_to_bin
from gitdb.base import (
OInfo,
OStream
)
from gitdb.db import GitDB # @UnusedImport
from gitdb.db import LooseObjectDB
from .exc import (
GitCommandError,
BadObject
)
__all__ = ('GitCmdObjectDB', 'GitDB')
# class GitCmdObjectDB(CompoundDB, ObjectDBW):
class GitCmdObjectDB(LooseObjectDB):
"""A database representing the default git object store, which includes loose
objects, pack files and an alternates file
It will create objects only in the loose object database.
:note: for now, we use the git command to do all the lookup, just until he
have packs and the other implementations
"""
def __init__(self, root_path, git):
"""Initialize this instance with the root and a git command"""
super(GitCmdObjectDB, self).__init__(root_path)
self._git = git
def info(self, sha):
hexsha, typename, size = self._git.get_object_header(bin_to_hex(sha))
return OInfo(hex_to_bin(hexsha), typename, size)
def stream(self, sha):
"""For now, all lookup is done by git itself"""
hexsha, typename, size, stream = self._git.stream_object_data(bin_to_hex(sha))
return OStream(hex_to_bin(hexsha), typename, size, stream)
# { Interface
def partial_to_complete_sha_hex(self, partial_hexsha):
""":return: Full binary 20 byte sha from the given partial hexsha
:raise AmbiguousObjectName:
:raise BadObject:
:note: currently we only raise BadObject as git does not communicate
AmbiguousObjects separately"""
try:
hexsha, typename, size = self._git.get_object_header(partial_hexsha) # @UnusedVariable
return hex_to_bin(hexsha)
except (GitCommandError, ValueError):
raise BadObject(partial_hexsha)
# END handle exceptions
#} END interface
@@ -0,0 +1,516 @@
# diff.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
import re
from git.cmd import handle_process_output
from git.compat import (
defenc,
PY3
)
from git.util import finalize_process, hex_to_bin
from .compat import binary_type
from .objects.blob import Blob
from .objects.util import mode_str_to_int
__all__ = ('Diffable', 'DiffIndex', 'Diff', 'NULL_TREE')
# Special object to compare against the empty tree in diffs
NULL_TREE = object()
_octal_byte_re = re.compile(b'\\\\([0-9]{3})')
def _octal_repl(matchobj):
value = matchobj.group(1)
value = int(value, 8)
if PY3:
value = bytes(bytearray((value,)))
else:
value = chr(value)
return value
def decode_path(path, has_ab_prefix=True):
if path == b'/dev/null':
return None
if path.startswith(b'"') and path.endswith(b'"'):
path = (path[1:-1].replace(b'\\n', b'\n')
.replace(b'\\t', b'\t')
.replace(b'\\"', b'"')
.replace(b'\\\\', b'\\'))
path = _octal_byte_re.sub(_octal_repl, path)
if has_ab_prefix:
assert path.startswith(b'a/') or path.startswith(b'b/')
path = path[2:]
return path
class Diffable(object):
"""Common interface for all object that can be diffed against another object of compatible type.
:note:
Subclasses require a repo member as it is the case for Object instances, for practical
reasons we do not derive from Object."""
__slots__ = ()
# standin indicating you want to diff against the index
class Index(object):
pass
def _process_diff_args(self, args):
"""
:return:
possibly altered version of the given args list.
Method is called right before git command execution.
Subclasses can use it to alter the behaviour of the superclass"""
return args
def diff(self, other=Index, paths=None, create_patch=False, **kwargs):
"""Creates diffs between two items being trees, trees and index or an
index and the working tree. It will detect renames automatically.
:param other:
Is the item to compare us with.
If None, we will be compared to the working tree.
If Treeish, it will be compared against the respective tree
If Index ( type ), it will be compared against the index.
If git.NULL_TREE, it will compare against the empty tree.
It defaults to Index to assure the method will not by-default fail
on bare repositories.
:param paths:
is a list of paths or a single path to limit the diff to.
It will only include at least one of the given path or paths.
:param create_patch:
If True, the returned Diff contains a detailed patch that if applied
makes the self to other. Patches are somewhat costly as blobs have to be read
and diffed.
:param kwargs:
Additional arguments passed to git-diff, such as
R=True to swap both sides of the diff.
:return: git.DiffIndex
:note:
On a bare repository, 'other' needs to be provided as Index or as
as Tree/Commit, or a git command error will occur"""
args = []
args.append("--abbrev=40") # we need full shas
args.append("--full-index") # get full index paths, not only filenames
args.append("-M") # check for renames, in both formats
if create_patch:
args.append("-p")
else:
args.append("--raw")
# in any way, assure we don't see colored output,
# fixes https://github.com/gitpython-developers/GitPython/issues/172
args.append('--no-color')
if paths is not None and not isinstance(paths, (tuple, list)):
paths = [paths]
diff_cmd = self.repo.git.diff
if other is self.Index:
args.insert(0, '--cached')
elif other is NULL_TREE:
args.insert(0, '-r') # recursive diff-tree
args.insert(0, '--root')
diff_cmd = self.repo.git.diff_tree
elif other is not None:
args.insert(0, '-r') # recursive diff-tree
args.insert(0, other)
diff_cmd = self.repo.git.diff_tree
args.insert(0, self)
# paths is list here or None
if paths:
args.append("--")
args.extend(paths)
# END paths handling
kwargs['as_process'] = True
proc = diff_cmd(*self._process_diff_args(args), **kwargs)
diff_method = (Diff._index_from_patch_format
if create_patch
else Diff._index_from_raw_format)
index = diff_method(self.repo, proc)
proc.wait()
return index
class DiffIndex(list):
"""Implements an Index for diffs, allowing a list of Diffs to be queried by
the diff properties.
The class improves the diff handling convenience"""
# change type invariant identifying possible ways a blob can have changed
# A = Added
# D = Deleted
# R = Renamed
# M = Modified
# T = Changed in the type
change_type = ("A", "D", "R", "M", "T")
def iter_change_type(self, change_type):
"""
:return:
iterator yielding Diff instances that match the given change_type
:param change_type:
Member of DiffIndex.change_type, namely:
* 'A' for added paths
* 'D' for deleted paths
* 'R' for renamed paths
* 'M' for paths with modified data
* 'T' for changed in the type paths
"""
if change_type not in self.change_type:
raise ValueError("Invalid change type: %s" % change_type)
for diff in self:
if diff.change_type == change_type:
yield diff
elif change_type == "A" and diff.new_file:
yield diff
elif change_type == "D" and diff.deleted_file:
yield diff
elif change_type == "R" and diff.renamed:
yield diff
elif change_type == "M" and diff.a_blob and diff.b_blob and diff.a_blob != diff.b_blob:
yield diff
# END for each diff
class Diff(object):
"""A Diff contains diff information between two Trees.
It contains two sides a and b of the diff, members are prefixed with
"a" and "b" respectively to inidcate that.
Diffs keep information about the changed blob objects, the file mode, renames,
deletions and new files.
There are a few cases where None has to be expected as member variable value:
``New File``::
a_mode is None
a_blob is None
a_path is None
``Deleted File``::
b_mode is None
b_blob is None
b_path is None
``Working Tree Blobs``
When comparing to working trees, the working tree blob will have a null hexsha
as a corresponding object does not yet exist. The mode will be null as well.
But the path will be available though.
If it is listed in a diff the working tree version of the file must
be different to the version in the index or tree, and hence has been modified."""
# precompiled regex
re_header = re.compile(br"""
^diff[ ]--git
[ ](?P<a_path_fallback>"?a/.+?"?)[ ](?P<b_path_fallback>"?b/.+?"?)\n
(?:^old[ ]mode[ ](?P<old_mode>\d+)\n
^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
(?:^similarity[ ]index[ ]\d+%\n
^rename[ ]from[ ](?P<rename_from>.*)\n
^rename[ ]to[ ](?P<rename_to>.*)(?:\n|$))?
(?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
(?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
(?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
\.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
(?:^---[ ](?P<a_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))?
(?:^\+\+\+[ ](?P<b_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))?
""", re.VERBOSE | re.MULTILINE)
# can be used for comparisons
NULL_HEX_SHA = "0" * 40
NULL_BIN_SHA = b"\0" * 20
__slots__ = ("a_blob", "b_blob", "a_mode", "b_mode", "a_rawpath", "b_rawpath",
"new_file", "deleted_file", "raw_rename_from", "raw_rename_to",
"diff", "change_type", "score")
def __init__(self, repo, a_rawpath, b_rawpath, a_blob_id, b_blob_id, a_mode,
b_mode, new_file, deleted_file, raw_rename_from,
raw_rename_to, diff, change_type, score):
self.a_mode = a_mode
self.b_mode = b_mode
assert a_rawpath is None or isinstance(a_rawpath, binary_type)
assert b_rawpath is None or isinstance(b_rawpath, binary_type)
self.a_rawpath = a_rawpath
self.b_rawpath = b_rawpath
if self.a_mode:
self.a_mode = mode_str_to_int(self.a_mode)
if self.b_mode:
self.b_mode = mode_str_to_int(self.b_mode)
if a_blob_id is None or a_blob_id == self.NULL_HEX_SHA:
self.a_blob = None
else:
self.a_blob = Blob(repo, hex_to_bin(a_blob_id), mode=self.a_mode, path=self.a_path)
if b_blob_id is None or b_blob_id == self.NULL_HEX_SHA:
self.b_blob = None
else:
self.b_blob = Blob(repo, hex_to_bin(b_blob_id), mode=self.b_mode, path=self.b_path)
self.new_file = new_file
self.deleted_file = deleted_file
# be clear and use None instead of empty strings
assert raw_rename_from is None or isinstance(raw_rename_from, binary_type)
assert raw_rename_to is None or isinstance(raw_rename_to, binary_type)
self.raw_rename_from = raw_rename_from or None
self.raw_rename_to = raw_rename_to or None
self.diff = diff
self.change_type = change_type
self.score = score
def __eq__(self, other):
for name in self.__slots__:
if getattr(self, name) != getattr(other, name):
return False
# END for each name
return True
def __ne__(self, other):
return not (self == other)
def __hash__(self):
return hash(tuple(getattr(self, n) for n in self.__slots__))
def __str__(self):
h = "%s"
if self.a_blob:
h %= self.a_blob.path
elif self.b_blob:
h %= self.b_blob.path
msg = ''
line = None # temp line
line_length = 0 # line length
for b, n in zip((self.a_blob, self.b_blob), ('lhs', 'rhs')):
if b:
line = "\n%s: %o | %s" % (n, b.mode, b.hexsha)
else:
line = "\n%s: None" % n
# END if blob is not None
line_length = max(len(line), line_length)
msg += line
# END for each blob
# add headline
h += '\n' + '=' * line_length
if self.deleted_file:
msg += '\nfile deleted in rhs'
if self.new_file:
msg += '\nfile added in rhs'
if self.rename_from:
msg += '\nfile renamed from %r' % self.rename_from
if self.rename_to:
msg += '\nfile renamed to %r' % self.rename_to
if self.diff:
msg += '\n---'
try:
msg += self.diff.decode(defenc)
except UnicodeDecodeError:
msg += 'OMITTED BINARY DATA'
# end handle encoding
msg += '\n---'
# END diff info
# Python2 silliness: have to assure we convert our likely to be unicode object to a string with the
# right encoding. Otherwise it tries to convert it using ascii, which may fail ungracefully
res = h + msg
if not PY3:
res = res.encode(defenc)
# end
return res
@property
def a_path(self):
return self.a_rawpath.decode(defenc, 'replace') if self.a_rawpath else None
@property
def b_path(self):
return self.b_rawpath.decode(defenc, 'replace') if self.b_rawpath else None
@property
def rename_from(self):
return self.raw_rename_from.decode(defenc, 'replace') if self.raw_rename_from else None
@property
def rename_to(self):
return self.raw_rename_to.decode(defenc, 'replace') if self.raw_rename_to else None
@property
def renamed(self):
""":returns: True if the blob of our diff has been renamed
:note: This property is deprecated, please use ``renamed_file`` instead.
"""
return self.renamed_file
@property
def renamed_file(self):
""":returns: True if the blob of our diff has been renamed
"""
return self.rename_from != self.rename_to
@classmethod
def _pick_best_path(cls, path_match, rename_match, path_fallback_match):
if path_match:
return decode_path(path_match)
if rename_match:
return decode_path(rename_match, has_ab_prefix=False)
if path_fallback_match:
return decode_path(path_fallback_match)
return None
@classmethod
def _index_from_patch_format(cls, repo, proc):
"""Create a new DiffIndex from the given text which must be in patch format
:param repo: is the repository we are operating on - it is required
:param stream: result of 'git diff' as a stream (supporting file protocol)
:return: git.DiffIndex """
## FIXME: Here SLURPING raw, need to re-phrase header-regexes linewise.
text = []
handle_process_output(proc, text.append, None, finalize_process, decode_streams=False)
# for now, we have to bake the stream
text = b''.join(text)
index = DiffIndex()
previous_header = None
for header in cls.re_header.finditer(text):
a_path_fallback, b_path_fallback, \
old_mode, new_mode, \
rename_from, rename_to, \
new_file_mode, deleted_file_mode, \
a_blob_id, b_blob_id, b_mode, \
a_path, b_path = header.groups()
new_file, deleted_file = bool(new_file_mode), bool(deleted_file_mode)
a_path = cls._pick_best_path(a_path, rename_from, a_path_fallback)
b_path = cls._pick_best_path(b_path, rename_to, b_path_fallback)
# Our only means to find the actual text is to see what has not been matched by our regex,
# and then retro-actively assign it to our index
if previous_header is not None:
index[-1].diff = text[previous_header.end():header.start()]
# end assign actual diff
# Make sure the mode is set if the path is set. Otherwise the resulting blob is invalid
# We just use the one mode we should have parsed
a_mode = old_mode or deleted_file_mode or (a_path and (b_mode or new_mode or new_file_mode))
b_mode = b_mode or new_mode or new_file_mode or (b_path and a_mode)
index.append(Diff(repo,
a_path,
b_path,
a_blob_id and a_blob_id.decode(defenc),
b_blob_id and b_blob_id.decode(defenc),
a_mode and a_mode.decode(defenc),
b_mode and b_mode.decode(defenc),
new_file, deleted_file,
rename_from,
rename_to,
None, None, None))
previous_header = header
# end for each header we parse
if index:
index[-1].diff = text[header.end():]
# end assign last diff
return index
@classmethod
def _index_from_raw_format(cls, repo, proc):
"""Create a new DiffIndex from the given stream which must be in raw format.
:return: git.DiffIndex"""
# handles
# :100644 100644 687099101... 37c5e30c8... M .gitignore
index = DiffIndex()
def handle_diff_line(line):
line = line.decode(defenc)
if not line.startswith(":"):
return
meta, _, path = line[1:].partition('\t')
old_mode, new_mode, a_blob_id, b_blob_id, _change_type = meta.split(None, 4)
# Change type can be R100
# R: status letter
# 100: score (in case of copy and rename)
change_type = _change_type[0]
score_str = ''.join(_change_type[1:])
score = int(score_str) if score_str.isdigit() else None
path = path.strip()
a_path = path.encode(defenc)
b_path = path.encode(defenc)
deleted_file = False
new_file = False
rename_from = None
rename_to = None
# NOTE: We cannot conclude from the existence of a blob to change type
# as diffs with the working do not have blobs yet
if change_type == 'D':
b_blob_id = None
deleted_file = True
elif change_type == 'A':
a_blob_id = None
new_file = True
elif change_type == 'R':
a_path, b_path = path.split('\t', 1)
a_path = a_path.encode(defenc)
b_path = b_path.encode(defenc)
rename_from, rename_to = a_path, b_path
elif change_type == 'T':
# Nothing to do
pass
# END add/remove handling
diff = Diff(repo, a_path, b_path, a_blob_id, b_blob_id, old_mode, new_mode,
new_file, deleted_file, rename_from, rename_to, '',
change_type, score)
index.append(diff)
handle_process_output(proc, handle_diff_line, None, finalize_process, decode_streams=False)
return index
+132
View File
@@ -0,0 +1,132 @@
# exc.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
""" Module containing all exceptions thrown throughout the git package, """
from gitdb.exc import * # NOQA @UnusedWildImport
from git.compat import UnicodeMixin, safe_decode, string_types
class GitError(Exception):
""" Base class for all package exceptions """
class InvalidGitRepositoryError(GitError):
""" Thrown if the given repository appears to have an invalid format. """
class WorkTreeRepositoryUnsupported(InvalidGitRepositoryError):
""" Thrown to indicate we can't handle work tree repositories """
class NoSuchPathError(GitError, OSError):
""" Thrown if a path could not be access by the system. """
class CommandError(UnicodeMixin, GitError):
"""Base class for exceptions thrown at every stage of `Popen()` execution.
:param command:
A non-empty list of argv comprising the command-line.
"""
#: A unicode print-format with 2 `%s for `<cmdline>` and the rest,
#: e.g.
#: u"'%s' failed%s"
_msg = u"Cmd('%s') failed%s"
def __init__(self, command, status=None, stderr=None, stdout=None):
if not isinstance(command, (tuple, list)):
command = command.split()
self.command = command
self.status = status
if status:
if isinstance(status, Exception):
status = u"%s('%s')" % (type(status).__name__, safe_decode(str(status)))
else:
try:
status = u'exit code(%s)' % int(status)
except (ValueError, TypeError):
s = safe_decode(str(status))
status = u"'%s'" % s if isinstance(status, string_types) else s
self._cmd = safe_decode(command[0])
self._cmdline = u' '.join(safe_decode(i) for i in command)
self._cause = status and u" due to: %s" % status or "!"
self.stdout = stdout and u"\n stdout: '%s'" % safe_decode(stdout) or ''
self.stderr = stderr and u"\n stderr: '%s'" % safe_decode(stderr) or ''
def __unicode__(self):
return (self._msg + "\n cmdline: %s%s%s") % (
self._cmd, self._cause, self._cmdline, self.stdout, self.stderr)
class GitCommandNotFound(CommandError):
"""Thrown if we cannot find the `git` executable in the PATH or at the path given by
the GIT_PYTHON_GIT_EXECUTABLE environment variable"""
def __init__(self, command, cause):
super(GitCommandNotFound, self).__init__(command, cause)
self._msg = u"Cmd('%s') not found%s"
class GitCommandError(CommandError):
""" Thrown if execution of the git command fails with non-zero status code. """
def __init__(self, command, status, stderr=None, stdout=None):
super(GitCommandError, self).__init__(command, status, stderr, stdout)
class CheckoutError(GitError):
"""Thrown if a file could not be checked out from the index as it contained
changes.
The .failed_files attribute contains a list of relative paths that failed
to be checked out as they contained changes that did not exist in the index.
The .failed_reasons attribute contains a string informing about the actual
cause of the issue.
The .valid_files attribute contains a list of relative paths to files that
were checked out successfully and hence match the version stored in the
index"""
def __init__(self, message, failed_files, valid_files, failed_reasons):
Exception.__init__(self, message)
self.failed_files = failed_files
self.failed_reasons = failed_reasons
self.valid_files = valid_files
def __str__(self):
return Exception.__str__(self) + ":%s" % self.failed_files
class CacheError(GitError):
"""Base for all errors related to the git index, which is called cache internally"""
class UnmergedEntriesError(CacheError):
"""Thrown if an operation cannot proceed as there are still unmerged
entries in the cache"""
class HookExecutionError(CommandError):
"""Thrown if a hook exits with a non-zero exit code. It provides access to the exit code and the string returned
via standard output"""
def __init__(self, command, status, stderr=None, stdout=None):
super(HookExecutionError, self).__init__(command, status, stderr, stdout)
self._msg = u"Hook('%s') failed%s"
class RepositoryDirtyError(GitError):
"""Thrown whenever an operation on a repository fails as it has uncommitted changes that would be overwritten"""
def __init__(self, repo, message):
self.repo = repo
self.message = message
def __str__(self):
return "Operation cannot be performed on %r: %s" % (self.repo, self.message)
@@ -0,0 +1,6 @@
"""Initialize the index package"""
# flake8: noqa
from __future__ import absolute_import
from .base import *
from .typ import *
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,384 @@
# Contains standalone functions to accompany the index implementation and make it
# more versatile
# NOTE: Autodoc hates it if this is a docstring
from io import BytesIO
import os
from stat import (
S_IFDIR,
S_IFLNK,
S_ISLNK,
S_ISDIR,
S_IFMT,
S_IFREG,
)
import subprocess
from git.cmd import PROC_CREATIONFLAGS, handle_process_output
from git.compat import (
PY3,
defenc,
force_text,
force_bytes,
is_posix,
safe_encode,
safe_decode,
)
from git.exc import (
UnmergedEntriesError,
HookExecutionError
)
from git.objects.fun import (
tree_to_stream,
traverse_tree_recursive,
traverse_trees_recursive
)
from git.util import IndexFileSHA1Writer, finalize_process
from gitdb.base import IStream
from gitdb.typ import str_tree_type
import os.path as osp
from .typ import (
BaseIndexEntry,
IndexEntry,
CE_NAMEMASK,
CE_STAGESHIFT
)
from .util import (
pack,
unpack
)
S_IFGITLINK = S_IFLNK | S_IFDIR # a submodule
CE_NAMEMASK_INV = ~CE_NAMEMASK
__all__ = ('write_cache', 'read_cache', 'write_tree_from_cache', 'entry_key',
'stat_mode_to_index_mode', 'S_IFGITLINK', 'run_commit_hook', 'hook_path')
def hook_path(name, git_dir):
""":return: path to the given named hook in the given git repository directory"""
return osp.join(git_dir, 'hooks', name)
def run_commit_hook(name, index, *args):
"""Run the commit hook of the given name. Silently ignores hooks that do not exist.
:param name: name of hook, like 'pre-commit'
:param index: IndexFile instance
:param args: arguments passed to hook file
:raises HookExecutionError: """
hp = hook_path(name, index.repo.git_dir)
if not os.access(hp, os.X_OK):
return
env = os.environ.copy()
env['GIT_INDEX_FILE'] = safe_decode(index.path) if PY3 else safe_encode(index.path)
env['GIT_EDITOR'] = ':'
try:
cmd = subprocess.Popen([hp] + list(args),
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=index.repo.working_dir,
close_fds=is_posix,
creationflags=PROC_CREATIONFLAGS,)
except Exception as ex:
raise HookExecutionError(hp, ex)
else:
stdout = []
stderr = []
handle_process_output(cmd, stdout.append, stderr.append, finalize_process)
stdout = ''.join(stdout)
stderr = ''.join(stderr)
if cmd.returncode != 0:
stdout = force_text(stdout, defenc)
stderr = force_text(stderr, defenc)
raise HookExecutionError(hp, cmd.returncode, stderr, stdout)
# end handle return code
def stat_mode_to_index_mode(mode):
"""Convert the given mode from a stat call to the corresponding index mode
and return it"""
if S_ISLNK(mode): # symlinks
return S_IFLNK
if S_ISDIR(mode) or S_IFMT(mode) == S_IFGITLINK: # submodules
return S_IFGITLINK
return S_IFREG | 0o644 | (mode & 0o111) # blobs with or without executable bit
def write_cache(entries, stream, extension_data=None, ShaStreamCls=IndexFileSHA1Writer):
"""Write the cache represented by entries to a stream
:param entries: **sorted** list of entries
:param stream: stream to wrap into the AdapterStreamCls - it is used for
final output.
:param ShaStreamCls: Type to use when writing to the stream. It produces a sha
while writing to it, before the data is passed on to the wrapped stream
:param extension_data: any kind of data to write as a trailer, it must begin
a 4 byte identifier, followed by its size ( 4 bytes )"""
# wrap the stream into a compatible writer
stream = ShaStreamCls(stream)
tell = stream.tell
write = stream.write
# header
version = 2
write(b"DIRC")
write(pack(">LL", version, len(entries)))
# body
for entry in entries:
beginoffset = tell()
write(entry[4]) # ctime
write(entry[5]) # mtime
path = entry[3]
path = force_bytes(path, encoding=defenc)
plen = len(path) & CE_NAMEMASK # path length
assert plen == len(path), "Path %s too long to fit into index" % entry[3]
flags = plen | (entry[2] & CE_NAMEMASK_INV) # clear possible previous values
write(pack(">LLLLLL20sH", entry[6], entry[7], entry[0],
entry[8], entry[9], entry[10], entry[1], flags))
write(path)
real_size = ((tell() - beginoffset + 8) & ~7)
write(b"\0" * ((beginoffset + real_size) - tell()))
# END for each entry
# write previously cached extensions data
if extension_data is not None:
stream.write(extension_data)
# write the sha over the content
stream.write_sha()
def read_header(stream):
"""Return tuple(version_long, num_entries) from the given stream"""
type_id = stream.read(4)
if type_id != b"DIRC":
raise AssertionError("Invalid index file header: %r" % type_id)
version, num_entries = unpack(">LL", stream.read(4 * 2))
# TODO: handle version 3: extended data, see read-cache.c
assert version in (1, 2)
return version, num_entries
def entry_key(*entry):
""":return: Key suitable to be used for the index.entries dictionary
:param entry: One instance of type BaseIndexEntry or the path and the stage"""
if len(entry) == 1:
return (entry[0].path, entry[0].stage)
else:
return tuple(entry)
# END handle entry
def read_cache(stream):
"""Read a cache file from the given stream
:return: tuple(version, entries_dict, extension_data, content_sha)
* version is the integer version number
* entries dict is a dictionary which maps IndexEntry instances to a path at a stage
* extension_data is '' or 4 bytes of type + 4 bytes of size + size bytes
* content_sha is a 20 byte sha on all cache file contents"""
version, num_entries = read_header(stream)
count = 0
entries = {}
read = stream.read
tell = stream.tell
while count < num_entries:
beginoffset = tell()
ctime = unpack(">8s", read(8))[0]
mtime = unpack(">8s", read(8))[0]
(dev, ino, mode, uid, gid, size, sha, flags) = \
unpack(">LLLLLL20sH", read(20 + 4 * 6 + 2))
path_size = flags & CE_NAMEMASK
path = read(path_size).decode(defenc)
real_size = ((tell() - beginoffset + 8) & ~7)
read((beginoffset + real_size) - tell())
entry = IndexEntry((mode, sha, flags, path, ctime, mtime, dev, ino, uid, gid, size))
# entry_key would be the method to use, but we safe the effort
entries[(path, entry.stage)] = entry
count += 1
# END for each entry
# the footer contains extension data and a sha on the content so far
# Keep the extension footer,and verify we have a sha in the end
# Extension data format is:
# 4 bytes ID
# 4 bytes length of chunk
# repeated 0 - N times
extension_data = stream.read(~0)
assert len(extension_data) > 19, "Index Footer was not at least a sha on content as it was only %i bytes in size"\
% len(extension_data)
content_sha = extension_data[-20:]
# truncate the sha in the end as we will dynamically create it anyway
extension_data = extension_data[:-20]
return (version, entries, extension_data, content_sha)
def write_tree_from_cache(entries, odb, sl, si=0):
"""Create a tree from the given sorted list of entries and put the respective
trees into the given object database
:param entries: **sorted** list of IndexEntries
:param odb: object database to store the trees in
:param si: start index at which we should start creating subtrees
:param sl: slice indicating the range we should process on the entries list
:return: tuple(binsha, list(tree_entry, ...)) a tuple of a sha and a list of
tree entries being a tuple of hexsha, mode, name"""
tree_items = []
tree_items_append = tree_items.append
ci = sl.start
end = sl.stop
while ci < end:
entry = entries[ci]
if entry.stage != 0:
raise UnmergedEntriesError(entry)
# END abort on unmerged
ci += 1
rbound = entry.path.find('/', si)
if rbound == -1:
# its not a tree
tree_items_append((entry.binsha, entry.mode, entry.path[si:]))
else:
# find common base range
base = entry.path[si:rbound]
xi = ci
while xi < end:
oentry = entries[xi]
orbound = oentry.path.find('/', si)
if orbound == -1 or oentry.path[si:orbound] != base:
break
# END abort on base mismatch
xi += 1
# END find common base
# enter recursion
# ci - 1 as we want to count our current item as well
sha, tree_entry_list = write_tree_from_cache(entries, odb, slice(ci - 1, xi), rbound + 1) # @UnusedVariable
tree_items_append((sha, S_IFDIR, base))
# skip ahead
ci = xi
# END handle bounds
# END for each entry
# finally create the tree
sio = BytesIO()
tree_to_stream(tree_items, sio.write)
sio.seek(0)
istream = odb.store(IStream(str_tree_type, len(sio.getvalue()), sio))
return (istream.binsha, tree_items)
def _tree_entry_to_baseindexentry(tree_entry, stage):
return BaseIndexEntry((tree_entry[1], tree_entry[0], stage << CE_STAGESHIFT, tree_entry[2]))
def aggressive_tree_merge(odb, tree_shas):
"""
:return: list of BaseIndexEntries representing the aggressive merge of the given
trees. All valid entries are on stage 0, whereas the conflicting ones are left
on stage 1, 2 or 3, whereas stage 1 corresponds to the common ancestor tree,
2 to our tree and 3 to 'their' tree.
:param tree_shas: 1, 2 or 3 trees as identified by their binary 20 byte shas
If 1 or two, the entries will effectively correspond to the last given tree
If 3 are given, a 3 way merge is performed"""
out = []
out_append = out.append
# one and two way is the same for us, as we don't have to handle an existing
# index, instrea
if len(tree_shas) in (1, 2):
for entry in traverse_tree_recursive(odb, tree_shas[-1], ''):
out_append(_tree_entry_to_baseindexentry(entry, 0))
# END for each entry
return out
# END handle single tree
if len(tree_shas) > 3:
raise ValueError("Cannot handle %i trees at once" % len(tree_shas))
# three trees
for base, ours, theirs in traverse_trees_recursive(odb, tree_shas, ''):
if base is not None:
# base version exists
if ours is not None:
# ours exists
if theirs is not None:
# it exists in all branches, if it was changed in both
# its a conflict, otherwise we take the changed version
# This should be the most common branch, so it comes first
if(base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0]) or \
(base[1] != ours[1] and base[1] != theirs[1] and ours[1] != theirs[1]):
# changed by both
out_append(_tree_entry_to_baseindexentry(base, 1))
out_append(_tree_entry_to_baseindexentry(ours, 2))
out_append(_tree_entry_to_baseindexentry(theirs, 3))
elif base[0] != ours[0] or base[1] != ours[1]:
# only we changed it
out_append(_tree_entry_to_baseindexentry(ours, 0))
else:
# either nobody changed it, or they did. In either
# case, use theirs
out_append(_tree_entry_to_baseindexentry(theirs, 0))
# END handle modification
else:
if ours[0] != base[0] or ours[1] != base[1]:
# they deleted it, we changed it, conflict
out_append(_tree_entry_to_baseindexentry(base, 1))
out_append(_tree_entry_to_baseindexentry(ours, 2))
# else:
# we didn't change it, ignore
# pass
# END handle our change
# END handle theirs
else:
if theirs is None:
# deleted in both, its fine - its out
pass
else:
if theirs[0] != base[0] or theirs[1] != base[1]:
# deleted in ours, changed theirs, conflict
out_append(_tree_entry_to_baseindexentry(base, 1))
out_append(_tree_entry_to_baseindexentry(theirs, 3))
# END theirs changed
# else:
# theirs didn't change
# pass
# END handle theirs
# END handle ours
else:
# all three can't be None
if ours is None:
# added in their branch
out_append(_tree_entry_to_baseindexentry(theirs, 0))
elif theirs is None:
# added in our branch
out_append(_tree_entry_to_baseindexentry(ours, 0))
else:
# both have it, except for the base, see whether it changed
if ours[0] != theirs[0] or ours[1] != theirs[1]:
out_append(_tree_entry_to_baseindexentry(ours, 2))
out_append(_tree_entry_to_baseindexentry(theirs, 3))
else:
# it was added the same in both
out_append(_tree_entry_to_baseindexentry(ours, 0))
# END handle two items
# END handle heads
# END handle base exists
# END for each entries tuple
return out
@@ -0,0 +1,176 @@
"""Module with additional types used by the index"""
from binascii import b2a_hex
from .util import (
pack,
unpack
)
from git.objects import Blob
__all__ = ('BlobFilter', 'BaseIndexEntry', 'IndexEntry')
#{ Invariants
CE_NAMEMASK = 0x0fff
CE_STAGEMASK = 0x3000
CE_EXTENDED = 0x4000
CE_VALID = 0x8000
CE_STAGESHIFT = 12
#} END invariants
class BlobFilter(object):
"""
Predicate to be used by iter_blobs allowing to filter only return blobs which
match the given list of directories or files.
The given paths are given relative to the repository.
"""
__slots__ = 'paths'
def __init__(self, paths):
"""
:param paths:
tuple or list of paths which are either pointing to directories or
to files relative to the current repository
"""
self.paths = paths
def __call__(self, stage_blob):
path = stage_blob[1].path
for p in self.paths:
if path.startswith(p):
return True
# END for each path in filter paths
return False
class BaseIndexEntry(tuple):
"""Small Brother of an index entry which can be created to describe changes
done to the index in which case plenty of additional information is not required.
As the first 4 data members match exactly to the IndexEntry type, methods
expecting a BaseIndexEntry can also handle full IndexEntries even if they
use numeric indices for performance reasons. """
def __str__(self):
return "%o %s %i\t%s" % (self.mode, self.hexsha, self.stage, self.path)
def __repr__(self):
return "(%o, %s, %i, %s)" % (self.mode, self.hexsha, self.stage, self.path)
@property
def mode(self):
""" File Mode, compatible to stat module constants """
return self[0]
@property
def binsha(self):
"""binary sha of the blob """
return self[1]
@property
def hexsha(self):
"""hex version of our sha"""
return b2a_hex(self[1]).decode('ascii')
@property
def stage(self):
"""Stage of the entry, either:
* 0 = default stage
* 1 = stage before a merge or common ancestor entry in case of a 3 way merge
* 2 = stage of entries from the 'left' side of the merge
* 3 = stage of entries from the right side of the merge
:note: For more information, see http://www.kernel.org/pub/software/scm/git/docs/git-read-tree.html
"""
return (self[2] & CE_STAGEMASK) >> CE_STAGESHIFT
@property
def path(self):
""":return: our path relative to the repository working tree root"""
return self[3]
@property
def flags(self):
""":return: flags stored with this entry"""
return self[2]
@classmethod
def from_blob(cls, blob, stage=0):
""":return: Fully equipped BaseIndexEntry at the given stage"""
return cls((blob.mode, blob.binsha, stage << CE_STAGESHIFT, blob.path))
def to_blob(self, repo):
""":return: Blob using the information of this index entry"""
return Blob(repo, self.binsha, self.mode, self.path)
class IndexEntry(BaseIndexEntry):
"""Allows convenient access to IndexEntry data without completely unpacking it.
Attributes usully accessed often are cached in the tuple whereas others are
unpacked on demand.
See the properties for a mapping between names and tuple indices. """
@property
def ctime(self):
"""
:return:
Tuple(int_time_seconds_since_epoch, int_nano_seconds) of the
file's creation time"""
return unpack(">LL", self[4])
@property
def mtime(self):
"""See ctime property, but returns modification time """
return unpack(">LL", self[5])
@property
def dev(self):
""" Device ID """
return self[6]
@property
def inode(self):
""" Inode ID """
return self[7]
@property
def uid(self):
""" User ID """
return self[8]
@property
def gid(self):
""" Group ID """
return self[9]
@property
def size(self):
""":return: Uncompressed size of the blob """
return self[10]
@classmethod
def from_base(cls, base):
"""
:return:
Minimal entry as created from the given BaseIndexEntry instance.
Missing values will be set to null-like values
:param base: Instance of type BaseIndexEntry"""
time = pack(">LL", 0, 0)
return IndexEntry((base.mode, base.binsha, base.flags, base.path, time, time, 0, 0, 0, 0, 0))
@classmethod
def from_blob(cls, blob, stage=0):
""":return: Minimal entry resembling the given blob object"""
time = pack(">LL", 0, 0)
return IndexEntry((blob.mode, blob.binsha, stage << CE_STAGESHIFT, blob.path,
time, time, 0, 0, 0, 0, blob.size))
@@ -0,0 +1,99 @@
"""Module containing index utilities"""
from functools import wraps
import os
import struct
import tempfile
from git.compat import is_win
import os.path as osp
__all__ = ('TemporaryFileSwap', 'post_clear_cache', 'default_index', 'git_working_dir')
#{ Aliases
pack = struct.pack
unpack = struct.unpack
#} END aliases
class TemporaryFileSwap(object):
"""Utility class moving a file to a temporary location within the same directory
and moving it back on to where on object deletion."""
__slots__ = ("file_path", "tmp_file_path")
def __init__(self, file_path):
self.file_path = file_path
self.tmp_file_path = self.file_path + tempfile.mktemp('', '', '')
# it may be that the source does not exist
try:
os.rename(self.file_path, self.tmp_file_path)
except OSError:
pass
def __del__(self):
if osp.isfile(self.tmp_file_path):
if is_win and osp.exists(self.file_path):
os.remove(self.file_path)
os.rename(self.tmp_file_path, self.file_path)
# END temp file exists
#{ Decorators
def post_clear_cache(func):
"""Decorator for functions that alter the index using the git command. This would
invalidate our possibly existing entries dictionary which is why it must be
deleted to allow it to be lazily reread later.
:note:
This decorator will not be required once all functions are implemented
natively which in fact is possible, but probably not feasible performance wise.
"""
@wraps(func)
def post_clear_cache_if_not_raised(self, *args, **kwargs):
rval = func(self, *args, **kwargs)
self._delete_entries_cache()
return rval
# END wrapper method
return post_clear_cache_if_not_raised
def default_index(func):
"""Decorator assuring the wrapped method may only run if we are the default
repository index. This is as we rely on git commands that operate
on that index only. """
@wraps(func)
def check_default_index(self, *args, **kwargs):
if self._file_path != self._index_path():
raise AssertionError(
"Cannot call %r on indices that do not represent the default git index" % func.__name__)
return func(self, *args, **kwargs)
# END wrapper method
return check_default_index
def git_working_dir(func):
"""Decorator which changes the current working dir to the one of the git
repository in order to assure relative paths are handled correctly"""
@wraps(func)
def set_git_working_dir(self, *args, **kwargs):
cur_wd = os.getcwd()
os.chdir(self.repo.working_tree_dir)
try:
return func(self, *args, **kwargs)
finally:
os.chdir(cur_wd)
# END handle working dir
# END wrapper
return set_git_working_dir
#} END decorators
@@ -0,0 +1,26 @@
"""
Import all submodules main classes into the package space
"""
# flake8: noqa
from __future__ import absolute_import
import inspect
from .base import *
from .blob import *
from .commit import *
from .submodule import util as smutil
from .submodule.base import *
from .submodule.root import *
from .tag import *
from .tree import *
# Fix import dependency - add IndexObject to the util module, so that it can be
# imported by the submodule.base
smutil.IndexObject = IndexObject
smutil.Object = Object
del(smutil)
# must come after submodule was made available
__all__ = [name for name, obj in locals().items()
if not (name.startswith('_') or inspect.ismodule(obj))]
@@ -0,0 +1,182 @@
# base.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
from git.util import LazyMixin, join_path_native, stream_copy, bin_to_hex
import gitdb.typ as dbtyp
import os.path as osp
from .util import get_object_type_by_name
_assertion_msg_format = "Created object %r whose python type %r disagrees with the acutal git object type %r"
__all__ = ("Object", "IndexObject")
class Object(LazyMixin):
"""Implements an Object which may be Blobs, Trees, Commits and Tags"""
NULL_HEX_SHA = '0' * 40
NULL_BIN_SHA = b'\0' * 20
TYPES = (dbtyp.str_blob_type, dbtyp.str_tree_type, dbtyp.str_commit_type, dbtyp.str_tag_type)
__slots__ = ("repo", "binsha", "size")
type = None # to be set by subclass
def __init__(self, repo, binsha):
"""Initialize an object by identifying it by its binary sha.
All keyword arguments will be set on demand if None.
:param repo: repository this object is located in
:param binsha: 20 byte SHA1"""
super(Object, self).__init__()
self.repo = repo
self.binsha = binsha
assert len(binsha) == 20, "Require 20 byte binary sha, got %r, len = %i" % (binsha, len(binsha))
@classmethod
def new(cls, repo, id): # @ReservedAssignment
"""
:return: New Object instance of a type appropriate to the object type behind
id. The id of the newly created object will be a binsha even though
the input id may have been a Reference or Rev-Spec
:param id: reference, rev-spec, or hexsha
:note: This cannot be a __new__ method as it would always call __init__
with the input id which is not necessarily a binsha."""
return repo.rev_parse(str(id))
@classmethod
def new_from_sha(cls, repo, sha1):
"""
:return: new object instance of a type appropriate to represent the given
binary sha1
:param sha1: 20 byte binary sha1"""
if sha1 == cls.NULL_BIN_SHA:
# the NULL binsha is always the root commit
return get_object_type_by_name(b'commit')(repo, sha1)
# END handle special case
oinfo = repo.odb.info(sha1)
inst = get_object_type_by_name(oinfo.type)(repo, oinfo.binsha)
inst.size = oinfo.size
return inst
def _set_cache_(self, attr):
"""Retrieve object information"""
if attr == "size":
oinfo = self.repo.odb.info(self.binsha)
self.size = oinfo.size
# assert oinfo.type == self.type, _assertion_msg_format % (self.binsha, oinfo.type, self.type)
else:
super(Object, self)._set_cache_(attr)
def __eq__(self, other):
""":return: True if the objects have the same SHA1"""
if not hasattr(other, 'binsha'):
return False
return self.binsha == other.binsha
def __ne__(self, other):
""":return: True if the objects do not have the same SHA1 """
if not hasattr(other, 'binsha'):
return True
return self.binsha != other.binsha
def __hash__(self):
""":return: Hash of our id allowing objects to be used in dicts and sets"""
return hash(self.binsha)
def __str__(self):
""":return: string of our SHA1 as understood by all git commands"""
return self.hexsha
def __repr__(self):
""":return: string with pythonic representation of our object"""
return '<git.%s "%s">' % (self.__class__.__name__, self.hexsha)
@property
def hexsha(self):
""":return: 40 byte hex version of our 20 byte binary sha"""
# b2a_hex produces bytes
return bin_to_hex(self.binsha).decode('ascii')
@property
def data_stream(self):
""" :return: File Object compatible stream to the uncompressed raw data of the object
:note: returned streams must be read in order"""
return self.repo.odb.stream(self.binsha)
def stream_data(self, ostream):
"""Writes our data directly to the given output stream
:param ostream: File object compatible stream object.
:return: self"""
istream = self.repo.odb.stream(self.binsha)
stream_copy(istream, ostream)
return self
class IndexObject(Object):
"""Base for all objects that can be part of the index file , namely Tree, Blob and
SubModule objects"""
__slots__ = ("path", "mode")
# for compatibility with iterable lists
_id_attribute_ = 'path'
def __init__(self, repo, binsha, mode=None, path=None):
"""Initialize a newly instanced IndexObject
:param repo: is the Repo we are located in
:param binsha: 20 byte sha1
:param mode:
is the stat compatible file mode as int, use the stat module
to evaluate the information
:param path:
is the path to the file in the file system, relative to the git repository root, i.e.
file.ext or folder/other.ext
:note:
Path may not be set of the index object has been created directly as it cannot
be retrieved without knowing the parent tree."""
super(IndexObject, self).__init__(repo, binsha)
if mode is not None:
self.mode = mode
if path is not None:
self.path = path
def __hash__(self):
"""
:return:
Hash of our path as index items are uniquely identifiable by path, not
by their data !"""
return hash(self.path)
def _set_cache_(self, attr):
if attr in IndexObject.__slots__:
# they cannot be retrieved lateron ( not without searching for them )
raise AttributeError(
"Attribute '%s' unset: path and mode attributes must have been set during %s object creation"
% (attr, type(self).__name__))
else:
super(IndexObject, self)._set_cache_(attr)
# END handle slot attribute
@property
def name(self):
""":return: Name portion of the path, effectively being the basename"""
return osp.basename(self.path)
@property
def abspath(self):
"""
:return:
Absolute path to this index object in the file system ( as opposed to the
.path field which is a path relative to the git repository ).
The returned path will be native to the system and contains '\' on windows. """
return join_path_native(self.repo.working_tree_dir, self.path)
@@ -0,0 +1,33 @@
# blob.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
from mimetypes import guess_type
from . import base
__all__ = ('Blob', )
class Blob(base.IndexObject):
"""A Blob encapsulates a git blob object"""
DEFAULT_MIME_TYPE = "text/plain"
type = "blob"
# valid blob modes
executable_mode = 0o100755
file_mode = 0o100644
link_mode = 0o120000
__slots__ = ()
@property
def mime_type(self):
"""
:return: String describing the mime type of this file (based on the filename)
:note: Defaults to 'text/plain' in case the actual file type is unknown. """
guesses = None
if self.path:
guesses = guess_type(self.path)
return guesses and guesses[0] or self.DEFAULT_MIME_TYPE
@@ -0,0 +1,533 @@
# commit.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
from gitdb import IStream
from git.util import (
hex_to_bin,
Actor,
Iterable,
Stats,
finalize_process
)
from git.diff import Diffable
from .tree import Tree
from . import base
from .util import (
Traversable,
Serializable,
parse_date,
altz_to_utctz_str,
parse_actor_and_date,
from_timestamp,
)
from git.compat import text_type
from time import (
time,
daylight,
altzone,
timezone,
localtime
)
import os
from io import BytesIO
import logging
log = logging.getLogger('git.objects.commit')
log.addHandler(logging.NullHandler())
__all__ = ('Commit', )
class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
"""Wraps a git Commit object.
This class will act lazily on some of its attributes and will query the
value on demand only if it involves calling the git binary."""
# ENVIRONMENT VARIABLES
# read when creating new commits
env_author_date = "GIT_AUTHOR_DATE"
env_committer_date = "GIT_COMMITTER_DATE"
# CONFIGURATION KEYS
conf_encoding = 'i18n.commitencoding'
# INVARIANTS
default_encoding = "UTF-8"
# object configuration
type = "commit"
__slots__ = ("tree",
"author", "authored_date", "author_tz_offset",
"committer", "committed_date", "committer_tz_offset",
"message", "parents", "encoding", "gpgsig")
_id_attribute_ = "hexsha"
def __init__(self, repo, binsha, tree=None, author=None, authored_date=None, author_tz_offset=None,
committer=None, committed_date=None, committer_tz_offset=None,
message=None, parents=None, encoding=None, gpgsig=None):
"""Instantiate a new Commit. All keyword arguments taking None as default will
be implicitly set on first query.
:param binsha: 20 byte sha1
:param parents: tuple( Commit, ... )
is a tuple of commit ids or actual Commits
:param tree: Tree
Tree object
:param author: Actor
is the author Actor object
:param authored_date: int_seconds_since_epoch
is the authored DateTime - use time.gmtime() to convert it into a
different format
:param author_tz_offset: int_seconds_west_of_utc
is the timezone that the authored_date is in
:param committer: Actor
is the committer string
:param committed_date: int_seconds_since_epoch
is the committed DateTime - use time.gmtime() to convert it into a
different format
:param committer_tz_offset: int_seconds_west_of_utc
is the timezone that the committed_date is in
:param message: string
is the commit message
:param encoding: string
encoding of the message, defaults to UTF-8
:param parents:
List or tuple of Commit objects which are our parent(s) in the commit
dependency graph
:return: git.Commit
:note:
Timezone information is in the same format and in the same sign
as what time.altzone returns. The sign is inverted compared to git's
UTC timezone."""
super(Commit, self).__init__(repo, binsha)
if tree is not None:
assert isinstance(tree, Tree), "Tree needs to be a Tree instance, was %s" % type(tree)
if tree is not None:
self.tree = tree
if author is not None:
self.author = author
if authored_date is not None:
self.authored_date = authored_date
if author_tz_offset is not None:
self.author_tz_offset = author_tz_offset
if committer is not None:
self.committer = committer
if committed_date is not None:
self.committed_date = committed_date
if committer_tz_offset is not None:
self.committer_tz_offset = committer_tz_offset
if message is not None:
self.message = message
if parents is not None:
self.parents = parents
if encoding is not None:
self.encoding = encoding
if gpgsig is not None:
self.gpgsig = gpgsig
@classmethod
def _get_intermediate_items(cls, commit):
return commit.parents
def _set_cache_(self, attr):
if attr in Commit.__slots__:
# read the data in a chunk, its faster - then provide a file wrapper
binsha, typename, self.size, stream = self.repo.odb.stream(self.binsha) # @UnusedVariable
self._deserialize(BytesIO(stream.read()))
else:
super(Commit, self)._set_cache_(attr)
# END handle attrs
@property
def authored_datetime(self):
return from_timestamp(self.authored_date, self.author_tz_offset)
@property
def committed_datetime(self):
return from_timestamp(self.committed_date, self.committer_tz_offset)
@property
def summary(self):
""":return: First line of the commit message"""
return self.message.split('\n', 1)[0]
def count(self, paths='', **kwargs):
"""Count the number of commits reachable from this commit
:param paths:
is an optional path or a list of paths restricting the return value
to commits actually containing the paths
:param kwargs:
Additional options to be passed to git-rev-list. They must not alter
the output style of the command, or parsing will yield incorrect results
:return: int defining the number of reachable commits"""
# yes, it makes a difference whether empty paths are given or not in our case
# as the empty paths version will ignore merge commits for some reason.
if paths:
return len(self.repo.git.rev_list(self.hexsha, '--', paths, **kwargs).splitlines())
else:
return len(self.repo.git.rev_list(self.hexsha, **kwargs).splitlines())
@property
def name_rev(self):
"""
:return:
String describing the commits hex sha based on the closest Reference.
Mostly useful for UI purposes"""
return self.repo.git.name_rev(self)
@classmethod
def iter_items(cls, repo, rev, paths='', **kwargs):
"""Find all commits matching the given criteria.
:param repo: is the Repo
:param rev: revision specifier, see git-rev-parse for viable options
:param paths:
is an optional path or list of paths, if set only Commits that include the path
or paths will be considered
:param kwargs:
optional keyword arguments to git rev-list where
``max_count`` is the maximum number of commits to fetch
``skip`` is the number of commits to skip
``since`` all commits since i.e. '1970-01-01'
:return: iterator yielding Commit items"""
if 'pretty' in kwargs:
raise ValueError("--pretty cannot be used as parsing expects single sha's only")
# END handle pretty
# use -- in any case, to prevent possibility of ambiguous arguments
# see https://github.com/gitpython-developers/GitPython/issues/264
args = ['--']
if paths:
args.extend((paths, ))
# END if paths
proc = repo.git.rev_list(rev, args, as_process=True, **kwargs)
return cls._iter_from_process_or_stream(repo, proc)
def iter_parents(self, paths='', **kwargs):
"""Iterate _all_ parents of this commit.
:param paths:
Optional path or list of paths limiting the Commits to those that
contain at least one of the paths
:param kwargs: All arguments allowed by git-rev-list
:return: Iterator yielding Commit objects which are parents of self """
# skip ourselves
skip = kwargs.get("skip", 1)
if skip == 0: # skip ourselves
skip = 1
kwargs['skip'] = skip
return self.iter_items(self.repo, self, paths, **kwargs)
@property
def stats(self):
"""Create a git stat from changes between this commit and its first parent
or from all changes done if this is the very first commit.
:return: git.Stats"""
if not self.parents:
text = self.repo.git.diff_tree(self.hexsha, '--', numstat=True, root=True)
text2 = ""
for line in text.splitlines()[1:]:
(insertions, deletions, filename) = line.split("\t")
text2 += "%s\t%s\t%s\n" % (insertions, deletions, filename)
text = text2
else:
text = self.repo.git.diff(self.parents[0].hexsha, self.hexsha, '--', numstat=True)
return Stats._list_from_string(self.repo, text)
@classmethod
def _iter_from_process_or_stream(cls, repo, proc_or_stream):
"""Parse out commit information into a list of Commit objects
We expect one-line per commit, and parse the actual commit information directly
from our lighting fast object database
:param proc: git-rev-list process instance - one sha per line
:return: iterator returning Commit objects"""
stream = proc_or_stream
if not hasattr(stream, 'readline'):
stream = proc_or_stream.stdout
readline = stream.readline
while True:
line = readline()
if not line:
break
hexsha = line.strip()
if len(hexsha) > 40:
# split additional information, as returned by bisect for instance
hexsha, _ = line.split(None, 1)
# END handle extra info
assert len(hexsha) == 40, "Invalid line: %s" % hexsha
yield Commit(repo, hex_to_bin(hexsha))
# END for each line in stream
# TODO: Review this - it seems process handling got a bit out of control
# due to many developers trying to fix the open file handles issue
if hasattr(proc_or_stream, 'wait'):
finalize_process(proc_or_stream)
@classmethod
def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False, author=None, committer=None,
author_date=None, commit_date=None):
"""Commit the given tree, creating a commit object.
:param repo: Repo object the commit should be part of
:param tree: Tree object or hex or bin sha
the tree of the new commit
:param message: Commit message. It may be an empty string if no message is provided.
It will be converted to a string in any case.
:param parent_commits:
Optional Commit objects to use as parents for the new commit.
If empty list, the commit will have no parents at all and become
a root commit.
If None , the current head commit will be the parent of the
new commit object
:param head:
If True, the HEAD will be advanced to the new commit automatically.
Else the HEAD will remain pointing on the previous commit. This could
lead to undesired results when diffing files.
:param author: The name of the author, optional. If unset, the repository
configuration is used to obtain this value.
:param committer: The name of the committer, optional. If unset, the
repository configuration is used to obtain this value.
:param author_date: The timestamp for the author field
:param commit_date: The timestamp for the committer field
:return: Commit object representing the new commit
:note:
Additional information about the committer and Author are taken from the
environment or from the git configuration, see git-commit-tree for
more information"""
if parent_commits is None:
try:
parent_commits = [repo.head.commit]
except ValueError:
# empty repositories have no head commit
parent_commits = []
# END handle parent commits
else:
for p in parent_commits:
if not isinstance(p, cls):
raise ValueError("Parent commit '%r' must be of type %s" % (p, cls))
# end check parent commit types
# END if parent commits are unset
# retrieve all additional information, create a commit object, and
# serialize it
# Generally:
# * Environment variables override configuration values
# * Sensible defaults are set according to the git documentation
# COMMITER AND AUTHOR INFO
cr = repo.config_reader()
env = os.environ
committer = committer or Actor.committer(cr)
author = author or Actor.author(cr)
# PARSE THE DATES
unix_time = int(time())
is_dst = daylight and localtime().tm_isdst > 0
offset = altzone if is_dst else timezone
author_date_str = env.get(cls.env_author_date, '')
if author_date:
author_time, author_offset = parse_date(author_date)
elif author_date_str:
author_time, author_offset = parse_date(author_date_str)
else:
author_time, author_offset = unix_time, offset
# END set author time
committer_date_str = env.get(cls.env_committer_date, '')
if commit_date:
committer_time, committer_offset = parse_date(commit_date)
elif committer_date_str:
committer_time, committer_offset = parse_date(committer_date_str)
else:
committer_time, committer_offset = unix_time, offset
# END set committer time
# assume utf8 encoding
enc_section, enc_option = cls.conf_encoding.split('.')
conf_encoding = cr.get_value(enc_section, enc_option, cls.default_encoding)
# if the tree is no object, make sure we create one - otherwise
# the created commit object is invalid
if isinstance(tree, str):
tree = repo.tree(tree)
# END tree conversion
# CREATE NEW COMMIT
new_commit = cls(repo, cls.NULL_BIN_SHA, tree,
author, author_time, author_offset,
committer, committer_time, committer_offset,
message, parent_commits, conf_encoding)
stream = BytesIO()
new_commit._serialize(stream)
streamlen = stream.tell()
stream.seek(0)
istream = repo.odb.store(IStream(cls.type, streamlen, stream))
new_commit.binsha = istream.binsha
if head:
# need late import here, importing git at the very beginning throws
# as well ...
import git.refs
try:
repo.head.set_commit(new_commit, logmsg=message)
except ValueError:
# head is not yet set to the ref our HEAD points to
# Happens on first commit
master = git.refs.Head.create(repo, repo.head.ref, new_commit, logmsg="commit (initial): %s" % message)
repo.head.set_reference(master, logmsg='commit: Switching to %s' % master)
# END handle empty repositories
# END advance head handling
return new_commit
#{ Serializable Implementation
def _serialize(self, stream):
write = stream.write
write(("tree %s\n" % self.tree).encode('ascii'))
for p in self.parents:
write(("parent %s\n" % p).encode('ascii'))
a = self.author
aname = a.name
c = self.committer
fmt = "%s %s <%s> %s %s\n"
write((fmt % ("author", aname, a.email,
self.authored_date,
altz_to_utctz_str(self.author_tz_offset))).encode(self.encoding))
# encode committer
aname = c.name
write((fmt % ("committer", aname, c.email,
self.committed_date,
altz_to_utctz_str(self.committer_tz_offset))).encode(self.encoding))
if self.encoding != self.default_encoding:
write(("encoding %s\n" % self.encoding).encode('ascii'))
try:
if self.__getattribute__('gpgsig') is not None:
write(b"gpgsig")
for sigline in self.gpgsig.rstrip("\n").split("\n"):
write((" " + sigline + "\n").encode('ascii'))
except AttributeError:
pass
write(b"\n")
# write plain bytes, be sure its encoded according to our encoding
if isinstance(self.message, text_type):
write(self.message.encode(self.encoding))
else:
write(self.message)
# END handle encoding
return self
def _deserialize(self, stream):
""":param from_rev_list: if true, the stream format is coming from the rev-list command
Otherwise it is assumed to be a plain data stream from our object"""
readline = stream.readline
self.tree = Tree(self.repo, hex_to_bin(readline().split()[1]), Tree.tree_id << 12, '')
self.parents = []
next_line = None
while True:
parent_line = readline()
if not parent_line.startswith(b'parent'):
next_line = parent_line
break
# END abort reading parents
self.parents.append(type(self)(self.repo, hex_to_bin(parent_line.split()[-1].decode('ascii'))))
# END for each parent line
self.parents = tuple(self.parents)
# we don't know actual author encoding before we have parsed it, so keep the lines around
author_line = next_line
committer_line = readline()
# we might run into one or more mergetag blocks, skip those for now
next_line = readline()
while next_line.startswith(b'mergetag '):
next_line = readline()
while next_line.startswith(b' '):
next_line = readline()
# end skip mergetags
# now we can have the encoding line, or an empty line followed by the optional
# message.
self.encoding = self.default_encoding
self.gpgsig = None
# read headers
enc = next_line
buf = enc.strip()
while buf:
if buf[0:10] == b"encoding ":
self.encoding = buf[buf.find(' ') + 1:].decode('ascii')
elif buf[0:7] == b"gpgsig ":
sig = buf[buf.find(b' ') + 1:] + b"\n"
is_next_header = False
while True:
sigbuf = readline()
if not sigbuf:
break
if sigbuf[0:1] != b" ":
buf = sigbuf.strip()
is_next_header = True
break
sig += sigbuf[1:]
# end read all signature
self.gpgsig = sig.rstrip(b"\n").decode('ascii')
if is_next_header:
continue
buf = readline().strip()
# decode the authors name
try:
self.author, self.authored_date, self.author_tz_offset = \
parse_actor_and_date(author_line.decode(self.encoding, 'replace'))
except UnicodeDecodeError:
log.error("Failed to decode author line '%s' using encoding %s", author_line, self.encoding,
exc_info=True)
try:
self.committer, self.committed_date, self.committer_tz_offset = \
parse_actor_and_date(committer_line.decode(self.encoding, 'replace'))
except UnicodeDecodeError:
log.error("Failed to decode committer line '%s' using encoding %s", committer_line, self.encoding,
exc_info=True)
# END handle author's encoding
# a stream from our data simply gives us the plain message
# The end of our message stream is marked with a newline that we strip
self.message = stream.read()
try:
self.message = self.message.decode(self.encoding, 'replace')
except UnicodeDecodeError:
log.error("Failed to decode message '%s' using encoding %s", self.message, self.encoding, exc_info=True)
# END exception handling
return self
#} END serializable implementation
@@ -0,0 +1,207 @@
"""Module with functions which are supposed to be as fast as possible"""
from stat import S_ISDIR
from git.compat import (
byte_ord,
safe_decode,
defenc,
xrange,
text_type,
bchr
)
__all__ = ('tree_to_stream', 'tree_entries_from_data', 'traverse_trees_recursive',
'traverse_tree_recursive')
def tree_to_stream(entries, write):
"""Write the give list of entries into a stream using its write method
:param entries: **sorted** list of tuples with (binsha, mode, name)
:param write: write method which takes a data string"""
ord_zero = ord('0')
bit_mask = 7 # 3 bits set
for binsha, mode, name in entries:
mode_str = b''
for i in xrange(6):
mode_str = bchr(((mode >> (i * 3)) & bit_mask) + ord_zero) + mode_str
# END for each 8 octal value
# git slices away the first octal if its zero
if byte_ord(mode_str[0]) == ord_zero:
mode_str = mode_str[1:]
# END save a byte
# here it comes: if the name is actually unicode, the replacement below
# will not work as the binsha is not part of the ascii unicode encoding -
# hence we must convert to an utf8 string for it to work properly.
# According to my tests, this is exactly what git does, that is it just
# takes the input literally, which appears to be utf8 on linux.
if isinstance(name, text_type):
name = name.encode(defenc)
write(b''.join((mode_str, b' ', name, b'\0', binsha)))
# END for each item
def tree_entries_from_data(data):
"""Reads the binary representation of a tree and returns tuples of Tree items
:param data: data block with tree data (as bytes)
:return: list(tuple(binsha, mode, tree_relative_path), ...)"""
ord_zero = ord('0')
space_ord = ord(' ')
len_data = len(data)
i = 0
out = []
while i < len_data:
mode = 0
# read mode
# Some git versions truncate the leading 0, some don't
# The type will be extracted from the mode later
while byte_ord(data[i]) != space_ord:
# move existing mode integer up one level being 3 bits
# and add the actual ordinal value of the character
mode = (mode << 3) + (byte_ord(data[i]) - ord_zero)
i += 1
# END while reading mode
# byte is space now, skip it
i += 1
# parse name, it is NULL separated
ns = i
while byte_ord(data[i]) != 0:
i += 1
# END while not reached NULL
# default encoding for strings in git is utf8
# Only use the respective unicode object if the byte stream was encoded
name = data[ns:i]
name = safe_decode(name)
# byte is NULL, get next 20
i += 1
sha = data[i:i + 20]
i = i + 20
out.append((sha, mode, name))
# END for each byte in data stream
return out
def _find_by_name(tree_data, name, is_dir, start_at):
"""return data entry matching the given name and tree mode
or None.
Before the item is returned, the respective data item is set
None in the tree_data list to mark it done"""
try:
item = tree_data[start_at]
if item and item[2] == name and S_ISDIR(item[1]) == is_dir:
tree_data[start_at] = None
return item
except IndexError:
pass
# END exception handling
for index, item in enumerate(tree_data):
if item and item[2] == name and S_ISDIR(item[1]) == is_dir:
tree_data[index] = None
return item
# END if item matches
# END for each item
return None
def _to_full_path(item, path_prefix):
"""Rebuild entry with given path prefix"""
if not item:
return item
return (item[0], item[1], path_prefix + item[2])
def traverse_trees_recursive(odb, tree_shas, path_prefix):
"""
:return: list with entries according to the given binary tree-shas.
The result is encoded in a list
of n tuple|None per blob/commit, (n == len(tree_shas)), where
* [0] == 20 byte sha
* [1] == mode as int
* [2] == path relative to working tree root
The entry tuple is None if the respective blob/commit did not
exist in the given tree.
:param tree_shas: iterable of shas pointing to trees. All trees must
be on the same level. A tree-sha may be None in which case None
:param path_prefix: a prefix to be added to the returned paths on this level,
set it '' for the first iteration
:note: The ordering of the returned items will be partially lost"""
trees_data = []
nt = len(tree_shas)
for tree_sha in tree_shas:
if tree_sha is None:
data = []
else:
data = tree_entries_from_data(odb.stream(tree_sha).read())
# END handle muted trees
trees_data.append(data)
# END for each sha to get data for
out = []
out_append = out.append
# find all matching entries and recursively process them together if the match
# is a tree. If the match is a non-tree item, put it into the result.
# Processed items will be set None
for ti, tree_data in enumerate(trees_data):
for ii, item in enumerate(tree_data):
if not item:
continue
# END skip already done items
entries = [None for _ in range(nt)]
entries[ti] = item
sha, mode, name = item # its faster to unpack @UnusedVariable
is_dir = S_ISDIR(mode) # type mode bits
# find this item in all other tree data items
# wrap around, but stop one before our current index, hence
# ti+nt, not ti+1+nt
for tio in range(ti + 1, ti + nt):
tio = tio % nt
entries[tio] = _find_by_name(trees_data[tio], name, is_dir, ii)
# END for each other item data
# if we are a directory, enter recursion
if is_dir:
out.extend(traverse_trees_recursive(
odb, [((ei and ei[0]) or None) for ei in entries], path_prefix + name + '/'))
else:
out_append(tuple(_to_full_path(e, path_prefix) for e in entries))
# END handle recursion
# finally mark it done
tree_data[ii] = None
# END for each item
# we are done with one tree, set all its data empty
del(tree_data[:])
# END for each tree_data chunk
return out
def traverse_tree_recursive(odb, tree_sha, path_prefix):
"""
:return: list of entries of the tree pointed to by the binary tree_sha. An entry
has the following format:
* [0] 20 byte sha
* [1] mode as int
* [2] path relative to the repository
:param path_prefix: prefix to prepend to the front of all returned paths"""
entries = []
data = tree_entries_from_data(odb.stream(tree_sha).read())
# unpacking/packing is faster than accessing individual items
for sha, mode, name in data:
if S_ISDIR(mode):
entries.extend(traverse_tree_recursive(odb, sha, path_prefix + name + '/'))
else:
entries.append((sha, mode, path_prefix + name))
# END for each item
return entries
@@ -0,0 +1,2 @@
# NOTE: Cannot import anything here as the top-level _init_ has to handle
# our dependencies
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,350 @@
from .base import (
Submodule,
UpdateProgress
)
from .util import (
find_first_remote_branch
)
from git.exc import InvalidGitRepositoryError
import git
import logging
__all__ = ["RootModule", "RootUpdateProgress"]
log = logging.getLogger('git.objects.submodule.root')
log.addHandler(logging.NullHandler())
class RootUpdateProgress(UpdateProgress):
"""Utility class which adds more opcodes to the UpdateProgress"""
REMOVE, PATHCHANGE, BRANCHCHANGE, URLCHANGE = [
1 << x for x in range(UpdateProgress._num_op_codes, UpdateProgress._num_op_codes + 4)]
_num_op_codes = UpdateProgress._num_op_codes + 4
__slots__ = ()
BEGIN = RootUpdateProgress.BEGIN
END = RootUpdateProgress.END
REMOVE = RootUpdateProgress.REMOVE
BRANCHCHANGE = RootUpdateProgress.BRANCHCHANGE
URLCHANGE = RootUpdateProgress.URLCHANGE
PATHCHANGE = RootUpdateProgress.PATHCHANGE
class RootModule(Submodule):
"""A (virtual) Root of all submodules in the given repository. It can be used
to more easily traverse all submodules of the master repository"""
__slots__ = ()
k_root_name = '__ROOT__'
def __init__(self, repo):
# repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, ref=None)
super(RootModule, self).__init__(
repo,
binsha=self.NULL_BIN_SHA,
mode=self.k_default_mode,
path='',
name=self.k_root_name,
parent_commit=repo.head.commit,
url='',
branch_path=git.Head.to_full_path(self.k_head_default)
)
def _clear_cache(self):
"""May not do anything"""
pass
#{ Interface
def update(self, previous_commit=None, recursive=True, force_remove=False, init=True,
to_latest_revision=False, progress=None, dry_run=False, force_reset=False,
keep_going=False):
"""Update the submodules of this repository to the current HEAD commit.
This method behaves smartly by determining changes of the path of a submodules
repository, next to changes to the to-be-checked-out commit or the branch to be
checked out. This works if the submodules ID does not change.
Additionally it will detect addition and removal of submodules, which will be handled
gracefully.
:param previous_commit: If set to a commit'ish, the commit we should use
as the previous commit the HEAD pointed to before it was set to the commit it points to now.
If None, it defaults to HEAD@{1} otherwise
:param recursive: if True, the children of submodules will be updated as well
using the same technique
:param force_remove: If submodules have been deleted, they will be forcibly removed.
Otherwise the update may fail if a submodule's repository cannot be deleted as
changes have been made to it (see Submodule.update() for more information)
:param init: If we encounter a new module which would need to be initialized, then do it.
:param to_latest_revision: If True, instead of checking out the revision pointed to
by this submodule's sha, the checked out tracking branch will be merged with the
latest remote branch fetched from the repository's origin.
Unless force_reset is specified, a local tracking branch will never be reset into its past, therefore
the remote branch must be in the future for this to have an effect.
:param force_reset: if True, submodules may checkout or reset their branch even if the repository has
pending changes that would be overwritten, or if the local tracking branch is in the future of the
remote tracking branch and would be reset into its past.
:param progress: RootUpdateProgress instance or None if no progress should be sent
:param dry_run: if True, operations will not actually be performed. Progress messages
will change accordingly to indicate the WOULD DO state of the operation.
:param keep_going: if True, we will ignore but log all errors, and keep going recursively.
Unless dry_run is set as well, keep_going could cause subsequent/inherited errors you wouldn't see
otherwise.
In conjunction with dry_run, it can be useful to anticipate all errors when updating submodules
:return: self"""
if self.repo.bare:
raise InvalidGitRepositoryError("Cannot update submodules in bare repositories")
# END handle bare
if progress is None:
progress = RootUpdateProgress()
# END assure progress is set
prefix = ''
if dry_run:
prefix = 'DRY-RUN: '
repo = self.repo
try:
# SETUP BASE COMMIT
###################
cur_commit = repo.head.commit
if previous_commit is None:
try:
previous_commit = repo.commit(repo.head.log_entry(-1).oldhexsha)
if previous_commit.binsha == previous_commit.NULL_BIN_SHA:
raise IndexError
# END handle initial commit
except IndexError:
# in new repositories, there is no previous commit
previous_commit = cur_commit
# END exception handling
else:
previous_commit = repo.commit(previous_commit) # obtain commit object
# END handle previous commit
psms = self.list_items(repo, parent_commit=previous_commit)
sms = self.list_items(repo)
spsms = set(psms)
ssms = set(sms)
# HANDLE REMOVALS
###################
rrsm = (spsms - ssms)
len_rrsm = len(rrsm)
for i, rsm in enumerate(rrsm):
op = REMOVE
if i == 0:
op |= BEGIN
# END handle begin
# fake it into thinking its at the current commit to allow deletion
# of previous module. Trigger the cache to be updated before that
progress.update(op, i, len_rrsm, prefix + "Removing submodule %r at %s" % (rsm.name, rsm.abspath))
rsm._parent_commit = repo.head.commit
rsm.remove(configuration=False, module=True, force=force_remove, dry_run=dry_run)
if i == len_rrsm - 1:
op |= END
# END handle end
progress.update(op, i, len_rrsm, prefix + "Done removing submodule %r" % rsm.name)
# END for each removed submodule
# HANDLE PATH RENAMES
#####################
# url changes + branch changes
csms = (spsms & ssms)
len_csms = len(csms)
for i, csm in enumerate(csms):
psm = psms[csm.name]
sm = sms[csm.name]
# PATH CHANGES
##############
if sm.path != psm.path and psm.module_exists():
progress.update(BEGIN | PATHCHANGE, i, len_csms, prefix +
"Moving repository of submodule %r from %s to %s"
% (sm.name, psm.abspath, sm.abspath))
# move the module to the new path
if not dry_run:
psm.move(sm.path, module=True, configuration=False)
# END handle dry_run
progress.update(
END | PATHCHANGE, i, len_csms, prefix + "Done moving repository of submodule %r" % sm.name)
# END handle path changes
if sm.module_exists():
# HANDLE URL CHANGE
###################
if sm.url != psm.url:
# Add the new remote, remove the old one
# This way, if the url just changes, the commits will not
# have to be re-retrieved
nn = '__new_origin__'
smm = sm.module()
rmts = smm.remotes
# don't do anything if we already have the url we search in place
if len([r for r in rmts if r.url == sm.url]) == 0:
progress.update(BEGIN | URLCHANGE, i, len_csms, prefix +
"Changing url of submodule %r from %s to %s" % (sm.name, psm.url, sm.url))
if not dry_run:
assert nn not in [r.name for r in rmts]
smr = smm.create_remote(nn, sm.url)
smr.fetch(progress=progress)
# If we have a tracking branch, it should be available
# in the new remote as well.
if len([r for r in smr.refs if r.remote_head == sm.branch_name]) == 0:
raise ValueError(
"Submodule branch named %r was not available in new submodule remote at %r"
% (sm.branch_name, sm.url)
)
# END head is not detached
# now delete the changed one
rmt_for_deletion = None
for remote in rmts:
if remote.url == psm.url:
rmt_for_deletion = remote
break
# END if urls match
# END for each remote
# if we didn't find a matching remote, but have exactly one,
# we can safely use this one
if rmt_for_deletion is None:
if len(rmts) == 1:
rmt_for_deletion = rmts[0]
else:
# if we have not found any remote with the original url
# we may not have a name. This is a special case,
# and its okay to fail here
# Alternatively we could just generate a unique name and leave all
# existing ones in place
raise InvalidGitRepositoryError(
"Couldn't find original remote-repo at url %r" % psm.url)
# END handle one single remote
# END handle check we found a remote
orig_name = rmt_for_deletion.name
smm.delete_remote(rmt_for_deletion)
# NOTE: Currently we leave tags from the deleted remotes
# as well as separate tracking branches in the possibly totally
# changed repository ( someone could have changed the url to
# another project ). At some point, one might want to clean
# it up, but the danger is high to remove stuff the user
# has added explicitly
# rename the new remote back to what it was
smr.rename(orig_name)
# early on, we verified that the our current tracking branch
# exists in the remote. Now we have to assure that the
# sha we point to is still contained in the new remote
# tracking branch.
smsha = sm.binsha
found = False
rref = smr.refs[self.branch_name]
for c in rref.commit.traverse():
if c.binsha == smsha:
found = True
break
# END traverse all commits in search for sha
# END for each commit
if not found:
# adjust our internal binsha to use the one of the remote
# this way, it will be checked out in the next step
# This will change the submodule relative to us, so
# the user will be able to commit the change easily
log.warn("Current sha %s was not contained in the tracking\
branch at the new remote, setting it the the remote's tracking branch", sm.hexsha)
sm.binsha = rref.commit.binsha
# END reset binsha
# NOTE: All checkout is performed by the base implementation of update
# END handle dry_run
progress.update(
END | URLCHANGE, i, len_csms, prefix + "Done adjusting url of submodule %r" % (sm.name))
# END skip remote handling if new url already exists in module
# END handle url
# HANDLE PATH CHANGES
#####################
if sm.branch_path != psm.branch_path:
# finally, create a new tracking branch which tracks the
# new remote branch
progress.update(BEGIN | BRANCHCHANGE, i, len_csms, prefix +
"Changing branch of submodule %r from %s to %s"
% (sm.name, psm.branch_path, sm.branch_path))
if not dry_run:
smm = sm.module()
smmr = smm.remotes
# As the branch might not exist yet, we will have to fetch all remotes to be sure ... .
for remote in smmr:
remote.fetch(progress=progress)
# end for each remote
try:
tbr = git.Head.create(smm, sm.branch_name, logmsg='branch: Created from HEAD')
except OSError:
# ... or reuse the existing one
tbr = git.Head(smm, sm.branch_path)
# END assure tracking branch exists
tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch_name))
# NOTE: All head-resetting is done in the base implementation of update
# but we will have to checkout the new branch here. As it still points to the currently
# checkout out commit, we don't do any harm.
# As we don't want to update working-tree or index, changing the ref is all there is to do
smm.head.reference = tbr
# END handle dry_run
progress.update(
END | BRANCHCHANGE, i, len_csms, prefix + "Done changing branch of submodule %r" % sm.name)
# END handle branch
# END handle
# END for each common submodule
except Exception as err:
if not keep_going:
raise
log.error(str(err))
# end handle keep_going
# FINALLY UPDATE ALL ACTUAL SUBMODULES
######################################
for sm in sms:
# update the submodule using the default method
sm.update(recursive=False, init=init, to_latest_revision=to_latest_revision,
progress=progress, dry_run=dry_run, force=force_reset, keep_going=keep_going)
# update recursively depth first - question is which inconsitent
# state will be better in case it fails somewhere. Defective branch
# or defective depth. The RootSubmodule type will never process itself,
# which was done in the previous expression
if recursive:
# the module would exist by now if we are not in dry_run mode
if sm.module_exists():
type(self)(sm.module()).update(recursive=True, force_remove=force_remove,
init=init, to_latest_revision=to_latest_revision,
progress=progress, dry_run=dry_run, force_reset=force_reset,
keep_going=keep_going)
# END handle dry_run
# END handle recursive
# END for each submodule to update
return self
def module(self):
""":return: the actual repository containing the submodules"""
return self.repo
#} END interface
#} END classes
@@ -0,0 +1,94 @@
import git
from git.exc import InvalidGitRepositoryError
from git.config import GitConfigParser
from io import BytesIO
import weakref
__all__ = ('sm_section', 'sm_name', 'mkhead', 'find_first_remote_branch',
'SubmoduleConfigParser')
#{ Utilities
def sm_section(name):
""":return: section title used in .gitmodules configuration file"""
return 'submodule "%s"' % name
def sm_name(section):
""":return: name of the submodule as parsed from the section name"""
section = section.strip()
return section[11:-1]
def mkhead(repo, path):
""":return: New branch/head instance"""
return git.Head(repo, git.Head.to_full_path(path))
def find_first_remote_branch(remotes, branch_name):
"""Find the remote branch matching the name of the given branch or raise InvalidGitRepositoryError"""
for remote in remotes:
try:
return remote.refs[branch_name]
except IndexError:
continue
# END exception handling
# END for remote
raise InvalidGitRepositoryError("Didn't find remote branch '%r' in any of the given remotes" % branch_name)
#} END utilities
#{ Classes
class SubmoduleConfigParser(GitConfigParser):
"""
Catches calls to _write, and updates the .gitmodules blob in the index
with the new data, if we have written into a stream. Otherwise it will
add the local file to the index to make it correspond with the working tree.
Additionally, the cache must be cleared
Please note that no mutating method will work in bare mode
"""
def __init__(self, *args, **kwargs):
self._smref = None
self._index = None
self._auto_write = True
super(SubmoduleConfigParser, self).__init__(*args, **kwargs)
#{ Interface
def set_submodule(self, submodule):
"""Set this instance's submodule. It must be called before
the first write operation begins"""
self._smref = weakref.ref(submodule)
def flush_to_index(self):
"""Flush changes in our configuration file to the index"""
assert self._smref is not None
# should always have a file here
assert not isinstance(self._file_or_files, BytesIO)
sm = self._smref()
if sm is not None:
index = self._index
if index is None:
index = sm.repo.index
# END handle index
index.add([sm.k_modules_file], write=self._auto_write)
sm._clear_cache()
# END handle weakref
#} END interface
#{ Overridden Methods
def write(self):
rval = super(SubmoduleConfigParser, self).write()
self.flush_to_index()
return rval
# END overridden methods
#} END classes
@@ -0,0 +1,75 @@
# objects.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
""" Module containing all object based types. """
from . import base
from .util import get_object_type_by_name, parse_actor_and_date
from ..util import hex_to_bin
from ..compat import defenc
__all__ = ("TagObject", )
class TagObject(base.Object):
"""Non-Lightweight tag carrying additional information about an object we are pointing to."""
type = "tag"
__slots__ = ("object", "tag", "tagger", "tagged_date", "tagger_tz_offset", "message")
def __init__(self, repo, binsha, object=None, tag=None, # @ReservedAssignment
tagger=None, tagged_date=None, tagger_tz_offset=None, message=None):
"""Initialize a tag object with additional data
:param repo: repository this object is located in
:param binsha: 20 byte SHA1
:param object: Object instance of object we are pointing to
:param tag: name of this tag
:param tagger: Actor identifying the tagger
:param tagged_date: int_seconds_since_epoch
is the DateTime of the tag creation - use time.gmtime to convert
it into a different format
:param tagged_tz_offset: int_seconds_west_of_utc is the timezone that the
authored_date is in, in a format similar to time.altzone"""
super(TagObject, self).__init__(repo, binsha)
if object is not None:
self.object = object
if tag is not None:
self.tag = tag
if tagger is not None:
self.tagger = tagger
if tagged_date is not None:
self.tagged_date = tagged_date
if tagger_tz_offset is not None:
self.tagger_tz_offset = tagger_tz_offset
if message is not None:
self.message = message
def _set_cache_(self, attr):
"""Cache all our attributes at once"""
if attr in TagObject.__slots__:
ostream = self.repo.odb.stream(self.binsha)
lines = ostream.read().decode(defenc).splitlines()
obj, hexsha = lines[0].split(" ") # object <hexsha> @UnusedVariable
type_token, type_name = lines[1].split(" ") # type <type_name> @UnusedVariable
self.object = \
get_object_type_by_name(type_name.encode('ascii'))(self.repo, hex_to_bin(hexsha))
self.tag = lines[2][4:] # tag <tag name>
if len(lines) > 3:
tagger_info = lines[3] # tagger <actor> <date>
self.tagger, self.tagged_date, self.tagger_tz_offset = parse_actor_and_date(tagger_info)
# line 4 empty - it could mark the beginning of the next header
# in case there really is no message, it would not exist. Otherwise
# a newline separates header from message
if len(lines) > 5:
self.message = "\n".join(lines[5:])
else:
self.message = ''
# END check our attributes
else:
super(TagObject, self)._set_cache_(attr)
@@ -0,0 +1,341 @@
# tree.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
from git.util import join_path
import git.diff as diff
from git.util import to_bin_sha
from . import util
from .base import IndexObject
from .blob import Blob
from .submodule.base import Submodule
from git.compat import string_types
from .fun import (
tree_entries_from_data,
tree_to_stream
)
from git.compat import PY3
if PY3:
cmp = lambda a, b: (a > b) - (a < b)
__all__ = ("TreeModifier", "Tree")
def git_cmp(t1, t2):
a, b = t1[2], t2[2]
len_a, len_b = len(a), len(b)
min_len = min(len_a, len_b)
min_cmp = cmp(a[:min_len], b[:min_len])
if min_cmp:
return min_cmp
return len_a - len_b
def merge_sort(a, cmp):
if len(a) < 2:
return
mid = len(a) // 2
lefthalf = a[:mid]
righthalf = a[mid:]
merge_sort(lefthalf, cmp)
merge_sort(righthalf, cmp)
i = 0
j = 0
k = 0
while i < len(lefthalf) and j < len(righthalf):
if cmp(lefthalf[i], righthalf[j]) <= 0:
a[k] = lefthalf[i]
i = i + 1
else:
a[k] = righthalf[j]
j = j + 1
k = k + 1
while i < len(lefthalf):
a[k] = lefthalf[i]
i = i + 1
k = k + 1
while j < len(righthalf):
a[k] = righthalf[j]
j = j + 1
k = k + 1
class TreeModifier(object):
"""A utility class providing methods to alter the underlying cache in a list-like fashion.
Once all adjustments are complete, the _cache, which really is a reference to
the cache of a tree, will be sorted. Assuring it will be in a serializable state"""
__slots__ = '_cache'
def __init__(self, cache):
self._cache = cache
def _index_by_name(self, name):
""":return: index of an item with name, or -1 if not found"""
for i, t in enumerate(self._cache):
if t[2] == name:
return i
# END found item
# END for each item in cache
return -1
#{ Interface
def set_done(self):
"""Call this method once you are done modifying the tree information.
It may be called several times, but be aware that each call will cause
a sort operation
:return self:"""
merge_sort(self._cache, git_cmp)
return self
#} END interface
#{ Mutators
def add(self, sha, mode, name, force=False):
"""Add the given item to the tree. If an item with the given name already
exists, nothing will be done, but a ValueError will be raised if the
sha and mode of the existing item do not match the one you add, unless
force is True
:param sha: The 20 or 40 byte sha of the item to add
:param mode: int representing the stat compatible mode of the item
:param force: If True, an item with your name and information will overwrite
any existing item with the same name, no matter which information it has
:return: self"""
if '/' in name:
raise ValueError("Name must not contain '/' characters")
if (mode >> 12) not in Tree._map_id_to_type:
raise ValueError("Invalid object type according to mode %o" % mode)
sha = to_bin_sha(sha)
index = self._index_by_name(name)
item = (sha, mode, name)
if index == -1:
self._cache.append(item)
else:
if force:
self._cache[index] = item
else:
ex_item = self._cache[index]
if ex_item[0] != sha or ex_item[1] != mode:
raise ValueError("Item %r existed with different properties" % name)
# END handle mismatch
# END handle force
# END handle name exists
return self
def add_unchecked(self, binsha, mode, name):
"""Add the given item to the tree, its correctness is assumed, which
puts the caller into responsibility to assure the input is correct.
For more information on the parameters, see ``add``
:param binsha: 20 byte binary sha"""
self._cache.append((binsha, mode, name))
def __delitem__(self, name):
"""Deletes an item with the given name if it exists"""
index = self._index_by_name(name)
if index > -1:
del(self._cache[index])
#} END mutators
class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable):
"""Tree objects represent an ordered list of Blobs and other Trees.
``Tree as a list``::
Access a specific blob using the
tree['filename'] notation.
You may as well access by index
blob = tree[0]
"""
type = "tree"
__slots__ = "_cache"
# actual integer ids for comparison
commit_id = 0o16 # equals stat.S_IFDIR | stat.S_IFLNK - a directory link
blob_id = 0o10
symlink_id = 0o12
tree_id = 0o04
_map_id_to_type = {
commit_id: Submodule,
blob_id: Blob,
symlink_id: Blob
# tree id added once Tree is defined
}
def __init__(self, repo, binsha, mode=tree_id << 12, path=None):
super(Tree, self).__init__(repo, binsha, mode, path)
@classmethod
def _get_intermediate_items(cls, index_object):
if index_object.type == "tree":
return tuple(index_object._iter_convert_to_object(index_object._cache))
return ()
def _set_cache_(self, attr):
if attr == "_cache":
# Set the data when we need it
ostream = self.repo.odb.stream(self.binsha)
self._cache = tree_entries_from_data(ostream.read())
else:
super(Tree, self)._set_cache_(attr)
# END handle attribute
def _iter_convert_to_object(self, iterable):
"""Iterable yields tuples of (binsha, mode, name), which will be converted
to the respective object representation"""
for binsha, mode, name in iterable:
path = join_path(self.path, name)
try:
yield self._map_id_to_type[mode >> 12](self.repo, binsha, mode, path)
except KeyError:
raise TypeError("Unknown mode %o found in tree data for path '%s'" % (mode, path))
# END for each item
def join(self, file):
"""Find the named object in this tree's contents
:return: ``git.Blob`` or ``git.Tree`` or ``git.Submodule``
:raise KeyError: if given file or tree does not exist in tree"""
msg = "Blob or Tree named %r not found"
if '/' in file:
tree = self
item = self
tokens = file.split('/')
for i, token in enumerate(tokens):
item = tree[token]
if item.type == 'tree':
tree = item
else:
# safety assertion - blobs are at the end of the path
if i != len(tokens) - 1:
raise KeyError(msg % file)
return item
# END handle item type
# END for each token of split path
if item == self:
raise KeyError(msg % file)
return item
else:
for info in self._cache:
if info[2] == file: # [2] == name
return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1],
join_path(self.path, info[2]))
# END for each obj
raise KeyError(msg % file)
# END handle long paths
def __div__(self, file):
"""For PY2 only"""
return self.join(file)
def __truediv__(self, file):
"""For PY3 only"""
return self.join(file)
@property
def trees(self):
""":return: list(Tree, ...) list of trees directly below this tree"""
return [i for i in self if i.type == "tree"]
@property
def blobs(self):
""":return: list(Blob, ...) list of blobs directly below this tree"""
return [i for i in self if i.type == "blob"]
@property
def cache(self):
"""
:return: An object allowing to modify the internal cache. This can be used
to change the tree's contents. When done, make sure you call ``set_done``
on the tree modifier, or serialization behaviour will be incorrect.
See the ``TreeModifier`` for more information on how to alter the cache"""
return TreeModifier(self._cache)
def traverse(self, predicate=lambda i, d: True,
prune=lambda i, d: False, depth=-1, branch_first=True,
visit_once=False, ignore_self=1):
"""For documentation, see util.Traversable.traverse
Trees are set to visit_once = False to gain more performance in the traversal"""
return super(Tree, self).traverse(predicate, prune, depth, branch_first, visit_once, ignore_self)
# List protocol
def __getslice__(self, i, j):
return list(self._iter_convert_to_object(self._cache[i:j]))
def __iter__(self):
return self._iter_convert_to_object(self._cache)
def __len__(self):
return len(self._cache)
def __getitem__(self, item):
if isinstance(item, int):
info = self._cache[item]
return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1], join_path(self.path, info[2]))
if isinstance(item, string_types):
# compatibility
return self.join(item)
# END index is basestring
raise TypeError("Invalid index type: %r" % item)
def __contains__(self, item):
if isinstance(item, IndexObject):
for info in self._cache:
if item.binsha == info[0]:
return True
# END compare sha
# END for each entry
# END handle item is index object
# compatibility
# treat item as repo-relative path
path = self.path
for info in self._cache:
if item == join_path(path, info[2]):
return True
# END for each item
return False
def __reversed__(self):
return reversed(self._iter_convert_to_object(self._cache))
def _serialize(self, stream):
"""Serialize this tree into the stream. Please note that we will assume
our tree data to be in a sorted state. If this is not the case, serialization
will not generate a correct tree representation as these are assumed to be sorted
by algorithms"""
tree_to_stream(self._cache, stream.write)
return self
def _deserialize(self, stream):
self._cache = tree_entries_from_data(stream.read())
return self
# END tree
# finalize map definition
Tree._map_id_to_type[Tree.tree_id] = Tree
#
@@ -0,0 +1,363 @@
# util.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Module for general utility functions"""
from git.util import (
IterableList,
Actor
)
import re
from collections import deque as Deque
from string import digits
import time
import calendar
from datetime import datetime, timedelta, tzinfo
__all__ = ('get_object_type_by_name', 'parse_date', 'parse_actor_and_date',
'ProcessStreamAdapter', 'Traversable', 'altz_to_utctz_str', 'utctz_to_altz',
'verify_utctz', 'Actor', 'tzoffset', 'utc')
ZERO = timedelta(0)
#{ Functions
def mode_str_to_int(modestr):
"""
:param modestr: string like 755 or 644 or 100644 - only the last 6 chars will be used
:return:
String identifying a mode compatible to the mode methods ids of the
stat module regarding the rwx permissions for user, group and other,
special flags and file system flags, i.e. whether it is a symlink
for example."""
mode = 0
for iteration, char in enumerate(reversed(modestr[-6:])):
mode += int(char) << iteration * 3
# END for each char
return mode
def get_object_type_by_name(object_type_name):
"""
:return: type suitable to handle the given object type name.
Use the type to create new instances.
:param object_type_name: Member of TYPES
:raise ValueError: In case object_type_name is unknown"""
if object_type_name == b"commit":
from . import commit
return commit.Commit
elif object_type_name == b"tag":
from . import tag
return tag.TagObject
elif object_type_name == b"blob":
from . import blob
return blob.Blob
elif object_type_name == b"tree":
from . import tree
return tree.Tree
else:
raise ValueError("Cannot handle unknown object type: %s" % object_type_name)
def utctz_to_altz(utctz):
"""we convert utctz to the timezone in seconds, it is the format time.altzone
returns. Git stores it as UTC timezone which has the opposite sign as well,
which explains the -1 * ( that was made explicit here )
:param utctz: git utc timezone string, i.e. +0200"""
return -1 * int(float(utctz) / 100 * 3600)
def altz_to_utctz_str(altz):
"""As above, but inverses the operation, returning a string that can be used
in commit objects"""
utci = -1 * int((float(altz) / 3600) * 100)
utcs = str(abs(utci))
utcs = "0" * (4 - len(utcs)) + utcs
prefix = (utci < 0 and '-') or '+'
return prefix + utcs
def verify_utctz(offset):
""":raise ValueError: if offset is incorrect
:return: offset"""
fmt_exc = ValueError("Invalid timezone offset format: %s" % offset)
if len(offset) != 5:
raise fmt_exc
if offset[0] not in "+-":
raise fmt_exc
if offset[1] not in digits or\
offset[2] not in digits or\
offset[3] not in digits or\
offset[4] not in digits:
raise fmt_exc
# END for each char
return offset
class tzoffset(tzinfo):
def __init__(self, secs_west_of_utc, name=None):
self._offset = timedelta(seconds=-secs_west_of_utc)
self._name = name or 'fixed'
def utcoffset(self, dt):
return self._offset
def tzname(self, dt):
return self._name
def dst(self, dt):
return ZERO
utc = tzoffset(0, 'UTC')
def from_timestamp(timestamp, tz_offset):
"""Converts a timestamp + tz_offset into an aware datetime instance."""
utc_dt = datetime.fromtimestamp(timestamp, utc)
try:
local_dt = utc_dt.astimezone(tzoffset(tz_offset))
return local_dt
except ValueError:
return utc_dt
def parse_date(string_date):
"""
Parse the given date as one of the following
* Git internal format: timestamp offset
* RFC 2822: Thu, 07 Apr 2005 22:13:13 +0200.
* ISO 8601 2005-04-07T22:13:13
The T can be a space as well
:return: Tuple(int(timestamp_UTC), int(offset)), both in seconds since epoch
:raise ValueError: If the format could not be understood
:note: Date can also be YYYY.MM.DD, MM/DD/YYYY and DD.MM.YYYY.
"""
# git time
try:
if string_date.count(' ') == 1 and string_date.rfind(':') == -1:
timestamp, offset = string_date.split()
timestamp = int(timestamp)
return timestamp, utctz_to_altz(verify_utctz(offset))
else:
offset = "+0000" # local time by default
if string_date[-5] in '-+':
offset = verify_utctz(string_date[-5:])
string_date = string_date[:-6] # skip space as well
# END split timezone info
offset = utctz_to_altz(offset)
# now figure out the date and time portion - split time
date_formats = []
splitter = -1
if ',' in string_date:
date_formats.append("%a, %d %b %Y")
splitter = string_date.rfind(' ')
else:
# iso plus additional
date_formats.append("%Y-%m-%d")
date_formats.append("%Y.%m.%d")
date_formats.append("%m/%d/%Y")
date_formats.append("%d.%m.%Y")
splitter = string_date.rfind('T')
if splitter == -1:
splitter = string_date.rfind(' ')
# END handle 'T' and ' '
# END handle rfc or iso
assert splitter > -1
# split date and time
time_part = string_date[splitter + 1:] # skip space
date_part = string_date[:splitter]
# parse time
tstruct = time.strptime(time_part, "%H:%M:%S")
for fmt in date_formats:
try:
dtstruct = time.strptime(date_part, fmt)
utctime = calendar.timegm((dtstruct.tm_year, dtstruct.tm_mon, dtstruct.tm_mday,
tstruct.tm_hour, tstruct.tm_min, tstruct.tm_sec,
dtstruct.tm_wday, dtstruct.tm_yday, tstruct.tm_isdst))
return int(utctime), offset
except ValueError:
continue
# END exception handling
# END for each fmt
# still here ? fail
raise ValueError("no format matched")
# END handle format
except Exception:
raise ValueError("Unsupported date format: %s" % string_date)
# END handle exceptions
# precompiled regex
_re_actor_epoch = re.compile(r'^.+? (.*) (\d+) ([+-]\d+).*$')
_re_only_actor = re.compile(r'^.+? (.*)$')
def parse_actor_and_date(line):
"""Parse out the actor (author or committer) info from a line like::
author Tom Preston-Werner <tom@mojombo.com> 1191999972 -0700
:return: [Actor, int_seconds_since_epoch, int_timezone_offset]"""
actor, epoch, offset = '', 0, 0
m = _re_actor_epoch.search(line)
if m:
actor, epoch, offset = m.groups()
else:
m = _re_only_actor.search(line)
actor = m.group(1) if m else line or ''
return (Actor._from_string(actor), int(epoch), utctz_to_altz(offset))
#} END functions
#{ Classes
class ProcessStreamAdapter(object):
"""Class wireing all calls to the contained Process instance.
Use this type to hide the underlying process to provide access only to a specified
stream. The process is usually wrapped into an AutoInterrupt class to kill
it if the instance goes out of scope."""
__slots__ = ("_proc", "_stream")
def __init__(self, process, stream_name):
self._proc = process
self._stream = getattr(process, stream_name)
def __getattr__(self, attr):
return getattr(self._stream, attr)
class Traversable(object):
"""Simple interface to perform depth-first or breadth-first traversals
into one direction.
Subclasses only need to implement one function.
Instances of the Subclass must be hashable"""
__slots__ = ()
@classmethod
def _get_intermediate_items(cls, item):
"""
Returns:
List of items connected to the given item.
Must be implemented in subclass
"""
raise NotImplementedError("To be implemented in subclass")
def list_traverse(self, *args, **kwargs):
"""
:return: IterableList with the results of the traversal as produced by
traverse()"""
out = IterableList(self._id_attribute_)
out.extend(self.traverse(*args, **kwargs))
return out
def traverse(self, predicate=lambda i, d: True,
prune=lambda i, d: False, depth=-1, branch_first=True,
visit_once=True, ignore_self=1, as_edge=False):
""":return: iterator yielding of items found when traversing self
:param predicate: f(i,d) returns False if item i at depth d should not be included in the result
:param prune:
f(i,d) return True if the search should stop at item i at depth d.
Item i will not be returned.
:param depth:
define at which level the iteration should not go deeper
if -1, there is no limit
if 0, you would effectively only get self, the root of the iteration
i.e. if 1, you would only get the first level of predecessors/successors
:param branch_first:
if True, items will be returned branch first, otherwise depth first
:param visit_once:
if True, items will only be returned once, although they might be encountered
several times. Loops are prevented that way.
:param ignore_self:
if True, self will be ignored and automatically pruned from
the result. Otherwise it will be the first item to be returned.
If as_edge is True, the source of the first edge is None
:param as_edge:
if True, return a pair of items, first being the source, second the
destination, i.e. tuple(src, dest) with the edge spanning from
source to destination"""
visited = set()
stack = Deque()
stack.append((0, self, None)) # self is always depth level 0
def addToStack(stack, item, branch_first, depth):
lst = self._get_intermediate_items(item)
if not lst:
return
if branch_first:
stack.extendleft((depth, i, item) for i in lst)
else:
reviter = ((depth, lst[i], item) for i in range(len(lst) - 1, -1, -1))
stack.extend(reviter)
# END addToStack local method
while stack:
d, item, src = stack.pop() # depth of item, item, item_source
if visit_once and item in visited:
continue
if visit_once:
visited.add(item)
rval = (as_edge and (src, item)) or item
if prune(rval, d):
continue
skipStartItem = ignore_self and (item is self)
if not skipStartItem and predicate(rval, d):
yield rval
# only continue to next level if this is appropriate !
nd = d + 1
if depth > -1 and nd > depth:
continue
addToStack(stack, item, branch_first, nd)
# END for each item on work stack
class Serializable(object):
"""Defines methods to serialize and deserialize objects from and into a data stream"""
__slots__ = ()
def _serialize(self, stream):
"""Serialize the data of this object into the given data stream
:note: a serialized object would ``_deserialize`` into the same object
:param stream: a file-like object
:return: self"""
raise NotImplementedError("To be implemented in subclass")
def _deserialize(self, stream):
"""Deserialize all information regarding this object from the stream
:param stream: a file-like object
:return: self"""
raise NotImplementedError("To be implemented in subclass")
@@ -0,0 +1,10 @@
# flake8: noqa
from __future__ import absolute_import
# import all modules in order, fix the names they require
from .symbolic import *
from .reference import *
from .head import *
from .tag import *
from .remote import *
from .log import *
@@ -0,0 +1,247 @@
from git.config import SectionConstraint
from git.util import join_path
from git.exc import GitCommandError
from .symbolic import SymbolicReference
from .reference import Reference
__all__ = ["HEAD", "Head"]
def strip_quotes(string):
if string.startswith('"') and string.endswith('"'):
return string[1:-1]
return string
class HEAD(SymbolicReference):
"""Special case of a Symbolic Reference as it represents the repository's
HEAD reference."""
_HEAD_NAME = 'HEAD'
_ORIG_HEAD_NAME = 'ORIG_HEAD'
__slots__ = ()
def __init__(self, repo, path=_HEAD_NAME):
if path != self._HEAD_NAME:
raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path))
super(HEAD, self).__init__(repo, path)
def orig_head(self):
"""
:return: SymbolicReference pointing at the ORIG_HEAD, which is maintained
to contain the previous value of HEAD"""
return SymbolicReference(self.repo, self._ORIG_HEAD_NAME)
def reset(self, commit='HEAD', index=True, working_tree=False,
paths=None, **kwargs):
"""Reset our HEAD to the given commit optionally synchronizing
the index and working tree. The reference we refer to will be set to
commit as well.
:param commit:
Commit object, Reference Object or string identifying a revision we
should reset HEAD to.
:param index:
If True, the index will be set to match the given commit. Otherwise
it will not be touched.
:param working_tree:
If True, the working tree will be forcefully adjusted to match the given
commit, possibly overwriting uncommitted changes without warning.
If working_tree is True, index must be true as well
:param paths:
Single path or list of paths relative to the git root directory
that are to be reset. This allows to partially reset individual files.
:param kwargs:
Additional arguments passed to git-reset.
:return: self"""
mode = "--soft"
if index:
mode = "--mixed"
# it appears, some git-versions declare mixed and paths deprecated
# see http://github.com/Byron/GitPython/issues#issue/2
if paths:
mode = None
# END special case
# END handle index
if working_tree:
mode = "--hard"
if not index:
raise ValueError("Cannot reset the working tree if the index is not reset as well")
# END working tree handling
try:
self.repo.git.reset(mode, commit, '--', paths, **kwargs)
except GitCommandError as e:
# git nowadays may use 1 as status to indicate there are still unstaged
# modifications after the reset
if e.status != 1:
raise
# END handle exception
return self
class Head(Reference):
"""A Head is a named reference to a Commit. Every Head instance contains a name
and a Commit object.
Examples::
>>> repo = Repo("/path/to/repo")
>>> head = repo.heads[0]
>>> head.name
'master'
>>> head.commit
<git.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
>>> head.commit.hexsha
'1c09f116cbc2cb4100fb6935bb162daa4723f455'"""
_common_path_default = "refs/heads"
k_config_remote = "remote"
k_config_remote_ref = "merge" # branch to merge from remote
@classmethod
def delete(cls, repo, *heads, **kwargs):
"""Delete the given heads
:param force:
If True, the heads will be deleted even if they are not yet merged into
the main development stream.
Default False"""
force = kwargs.get("force", False)
flag = "-d"
if force:
flag = "-D"
repo.git.branch(flag, *heads)
def set_tracking_branch(self, remote_reference):
"""
Configure this branch to track the given remote reference. This will alter
this branch's configuration accordingly.
:param remote_reference: The remote reference to track or None to untrack
any references
:return: self"""
from .remote import RemoteReference
if remote_reference is not None and not isinstance(remote_reference, RemoteReference):
raise ValueError("Incorrect parameter type: %r" % remote_reference)
# END handle type
with self.config_writer() as writer:
if remote_reference is None:
writer.remove_option(self.k_config_remote)
writer.remove_option(self.k_config_remote_ref)
if len(writer.options()) == 0:
writer.remove_section()
else:
writer.set_value(self.k_config_remote, remote_reference.remote_name)
writer.set_value(self.k_config_remote_ref, Head.to_full_path(remote_reference.remote_head))
return self
def tracking_branch(self):
"""
:return: The remote_reference we are tracking, or None if we are
not a tracking branch"""
from .remote import RemoteReference
reader = self.config_reader()
if reader.has_option(self.k_config_remote) and reader.has_option(self.k_config_remote_ref):
ref = Head(self.repo, Head.to_full_path(strip_quotes(reader.get_value(self.k_config_remote_ref))))
remote_refpath = RemoteReference.to_full_path(join_path(reader.get_value(self.k_config_remote), ref.name))
return RemoteReference(self.repo, remote_refpath)
# END handle have tracking branch
# we are not a tracking branch
return None
def rename(self, new_path, force=False):
"""Rename self to a new path
:param new_path:
Either a simple name or a path, i.e. new_name or features/new_name.
The prefix refs/heads is implied
:param force:
If True, the rename will succeed even if a head with the target name
already exists.
:return: self
:note: respects the ref log as git commands are used"""
flag = "-m"
if force:
flag = "-M"
self.repo.git.branch(flag, self, new_path)
self.path = "%s/%s" % (self._common_path_default, new_path)
return self
def checkout(self, force=False, **kwargs):
"""Checkout this head by setting the HEAD to this reference, by updating the index
to reflect the tree we point to and by updating the working tree to reflect
the latest index.
The command will fail if changed working tree files would be overwritten.
:param force:
If True, changes to the index and the working tree will be discarded.
If False, GitCommandError will be raised in that situation.
:param kwargs:
Additional keyword arguments to be passed to git checkout, i.e.
b='new_branch' to create a new branch at the given spot.
:return:
The active branch after the checkout operation, usually self unless
a new branch has been created.
If there is no active branch, as the HEAD is now detached, the HEAD
reference will be returned instead.
:note:
By default it is only allowed to checkout heads - everything else
will leave the HEAD detached which is allowed and possible, but remains
a special state that some tools might not be able to handle."""
kwargs['f'] = force
if kwargs['f'] is False:
kwargs.pop('f')
self.repo.git.checkout(self, **kwargs)
if self.repo.head.is_detached:
return self.repo.head
else:
return self.repo.active_branch
#{ Configuration
def _config_parser(self, read_only):
if read_only:
parser = self.repo.config_reader()
else:
parser = self.repo.config_writer()
# END handle parser instance
return SectionConstraint(parser, 'branch "%s"' % self.name)
def config_reader(self):
"""
:return: A configuration parser instance constrained to only read
this instance's values"""
return self._config_parser(read_only=True)
def config_writer(self):
"""
:return: A configuration writer instance with read-and write access
to options of this head"""
return self._config_parser(read_only=False)
#} END configuration
@@ -0,0 +1,317 @@
import re
import time
from git.compat import (
PY3,
xrange,
string_types,
defenc
)
from git.objects.util import (
parse_date,
Serializable,
altz_to_utctz_str,
)
from git.util import (
Actor,
LockedFD,
LockFile,
assure_directory_exists,
to_native_path,
bin_to_hex,
file_contents_ro_filepath
)
import os.path as osp
__all__ = ["RefLog", "RefLogEntry"]
class RefLogEntry(tuple):
"""Named tuple allowing easy access to the revlog data fields"""
_re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$')
__slots__ = ()
def __repr__(self):
"""Representation of ourselves in git reflog format"""
res = self.format()
if PY3:
return res
else:
# repr must return a string, which it will auto-encode from unicode using the default encoding.
# This usually fails, so we encode ourselves
return res.encode(defenc)
def format(self):
""":return: a string suitable to be placed in a reflog file"""
act = self.actor
time = self.time
return u"{} {} {} <{}> {!s} {}\t{}\n".format(self.oldhexsha,
self.newhexsha,
act.name,
act.email,
time[0],
altz_to_utctz_str(time[1]),
self.message)
@property
def oldhexsha(self):
"""The hexsha to the commit the ref pointed to before the change"""
return self[0]
@property
def newhexsha(self):
"""The hexsha to the commit the ref now points to, after the change"""
return self[1]
@property
def actor(self):
"""Actor instance, providing access"""
return self[2]
@property
def time(self):
"""time as tuple:
* [0] = int(time)
* [1] = int(timezone_offset) in time.altzone format """
return self[3]
@property
def message(self):
"""Message describing the operation that acted on the reference"""
return self[4]
@classmethod
def new(self, oldhexsha, newhexsha, actor, time, tz_offset, message):
""":return: New instance of a RefLogEntry"""
if not isinstance(actor, Actor):
raise ValueError("Need actor instance, got %s" % actor)
# END check types
return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), message))
@classmethod
def from_line(cls, line):
""":return: New RefLogEntry instance from the given revlog line.
:param line: line bytes without trailing newline
:raise ValueError: If line could not be parsed"""
line = line.decode(defenc)
fields = line.split('\t', 1)
if len(fields) == 1:
info, msg = fields[0], None
elif len(fields) == 2:
info, msg = fields
else:
raise ValueError("Line must have up to two TAB-separated fields."
" Got %s" % repr(line))
# END handle first split
oldhexsha = info[:40]
newhexsha = info[41:81]
for hexsha in (oldhexsha, newhexsha):
if not cls._re_hexsha_only.match(hexsha):
raise ValueError("Invalid hexsha: %r" % (hexsha,))
# END if hexsha re doesn't match
# END for each hexsha
email_end = info.find('>', 82)
if email_end == -1:
raise ValueError("Missing token: >")
# END handle missing end brace
actor = Actor._from_string(info[82:email_end + 1])
time, tz_offset = parse_date(info[email_end + 2:])
return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), msg))
class RefLog(list, Serializable):
"""A reflog contains reflog entries, each of which defines a certain state
of the head in question. Custom query methods allow to retrieve log entries
by date or by other criteria.
Reflog entries are ordered, the first added entry is first in the list, the last
entry, i.e. the last change of the head or reference, is last in the list."""
__slots__ = ('_path', )
def __new__(cls, filepath=None):
inst = super(RefLog, cls).__new__(cls)
return inst
def __init__(self, filepath=None):
"""Initialize this instance with an optional filepath, from which we will
initialize our data. The path is also used to write changes back using
the write() method"""
self._path = filepath
if filepath is not None:
self._read_from_file()
# END handle filepath
def _read_from_file(self):
try:
fmap = file_contents_ro_filepath(self._path, stream=True, allow_mmap=True)
except OSError:
# it is possible and allowed that the file doesn't exist !
return
# END handle invalid log
try:
self._deserialize(fmap)
finally:
fmap.close()
# END handle closing of handle
#{ Interface
@classmethod
def from_file(cls, filepath):
"""
:return: a new RefLog instance containing all entries from the reflog
at the given filepath
:param filepath: path to reflog
:raise ValueError: If the file could not be read or was corrupted in some way"""
return cls(filepath)
@classmethod
def path(cls, ref):
"""
:return: string to absolute path at which the reflog of the given ref
instance would be found. The path is not guaranteed to point to a valid
file though.
:param ref: SymbolicReference instance"""
return osp.join(ref.repo.git_dir, "logs", to_native_path(ref.path))
@classmethod
def iter_entries(cls, stream):
"""
:return: Iterator yielding RefLogEntry instances, one for each line read
sfrom the given stream.
:param stream: file-like object containing the revlog in its native format
or basestring instance pointing to a file to read"""
new_entry = RefLogEntry.from_line
if isinstance(stream, string_types):
stream = file_contents_ro_filepath(stream)
# END handle stream type
while True:
line = stream.readline()
if not line:
return
yield new_entry(line.strip())
# END endless loop
stream.close()
@classmethod
def entry_at(cls, filepath, index):
""":return: RefLogEntry at the given index
:param filepath: full path to the index file from which to read the entry
:param index: python list compatible index, i.e. it may be negative to
specify an entry counted from the end of the list
:raise IndexError: If the entry didn't exist
.. note:: This method is faster as it only parses the entry at index, skipping
all other lines. Nonetheless, the whole file has to be read if
the index is negative
"""
fp = open(filepath, 'rb')
if index < 0:
return RefLogEntry.from_line(fp.readlines()[index].strip())
else:
# read until index is reached
for i in xrange(index + 1):
line = fp.readline()
if not line:
break
# END abort on eof
# END handle runup
if i != index or not line:
raise IndexError
# END handle exception
return RefLogEntry.from_line(line.strip())
# END handle index
def to_file(self, filepath):
"""Write the contents of the reflog instance to a file at the given filepath.
:param filepath: path to file, parent directories are assumed to exist"""
lfd = LockedFD(filepath)
assure_directory_exists(filepath, is_file=True)
fp = lfd.open(write=True, stream=True)
try:
self._serialize(fp)
lfd.commit()
except Exception:
# on failure it rolls back automatically, but we make it clear
lfd.rollback()
raise
# END handle change
@classmethod
def append_entry(cls, config_reader, filepath, oldbinsha, newbinsha, message):
"""Append a new log entry to the revlog at filepath.
:param config_reader: configuration reader of the repository - used to obtain
user information. May also be an Actor instance identifying the committer directly.
May also be None
:param filepath: full path to the log file
:param oldbinsha: binary sha of the previous commit
:param newbinsha: binary sha of the current commit
:param message: message describing the change to the reference
:param write: If True, the changes will be written right away. Otherwise
the change will not be written
:return: RefLogEntry objects which was appended to the log
:note: As we are append-only, concurrent access is not a problem as we
do not interfere with readers."""
if len(oldbinsha) != 20 or len(newbinsha) != 20:
raise ValueError("Shas need to be given in binary format")
# END handle sha type
assure_directory_exists(filepath, is_file=True)
first_line = message.split('\n')[0]
committer = isinstance(config_reader, Actor) and config_reader or Actor.committer(config_reader)
entry = RefLogEntry((
bin_to_hex(oldbinsha).decode('ascii'),
bin_to_hex(newbinsha).decode('ascii'),
committer, (int(time.time()), time.altzone), first_line
))
lf = LockFile(filepath)
lf._obtain_lock_or_raise()
fd = open(filepath, 'ab')
try:
fd.write(entry.format().encode(defenc))
finally:
fd.close()
lf._release_lock()
# END handle write operation
return entry
def write(self):
"""Write this instance's data to the file we are originating from
:return: self"""
if self._path is None:
raise ValueError("Instance was not initialized with a path, use to_file(...) instead")
# END assert path
self.to_file(self._path)
return self
#} END interface
#{ Serializable Interface
def _serialize(self, stream):
write = stream.write
# write all entries
for e in self:
write(e.format().encode(defenc))
# END for each entry
def _deserialize(self, stream):
self.extend(self.iter_entries(stream))
#} END serializable interface
@@ -0,0 +1,126 @@
from git.util import (
LazyMixin,
Iterable,
)
from .symbolic import SymbolicReference
__all__ = ["Reference"]
#{ Utilities
def require_remote_ref_path(func):
"""A decorator raising a TypeError if we are not a valid remote, based on the path"""
def wrapper(self, *args):
if not self.is_remote():
raise ValueError("ref path does not point to a remote reference: %s" % self.path)
return func(self, *args)
# END wrapper
wrapper.__name__ = func.__name__
return wrapper
#}END utilities
class Reference(SymbolicReference, LazyMixin, Iterable):
"""Represents a named reference to any object. Subclasses may apply restrictions though,
i.e. Heads can only point to commits."""
__slots__ = ()
_points_to_commits_only = False
_resolve_ref_on_create = True
_common_path_default = "refs"
def __init__(self, repo, path, check_path=True):
"""Initialize this instance
:param repo: Our parent repository
:param path:
Path relative to the .git/ directory pointing to the ref in question, i.e.
refs/heads/master
:param check_path: if False, you can provide any path. Otherwise the path must start with the
default path prefix of this type."""
if check_path and not path.startswith(self._common_path_default + '/'):
raise ValueError("Cannot instantiate %r from path %s" % (self.__class__.__name__, path))
super(Reference, self).__init__(repo, path)
def __str__(self):
return self.name
#{ Interface
def set_object(self, object, logmsg=None): # @ReservedAssignment
"""Special version which checks if the head-log needs an update as well
:return: self"""
oldbinsha = None
if logmsg is not None:
head = self.repo.head
if not head.is_detached and head.ref == self:
oldbinsha = self.commit.binsha
# END handle commit retrieval
# END handle message is set
super(Reference, self).set_object(object, logmsg)
if oldbinsha is not None:
# /* from refs.c in git-source
# * Special hack: If a branch is updated directly and HEAD
# * points to it (may happen on the remote side of a push
# * for example) then logically the HEAD reflog should be
# * updated too.
# * A generic solution implies reverse symref information,
# * but finding all symrefs pointing to the given branch
# * would be rather costly for this rare event (the direct
# * update of a branch) to be worth it. So let's cheat and
# * check with HEAD only which should cover 99% of all usage
# * scenarios (even 100% of the default ones).
# */
self.repo.head.log_append(oldbinsha, logmsg)
# END check if the head
return self
# NOTE: Don't have to overwrite properties as the will only work without a the log
@property
def name(self):
""":return: (shortest) Name of this reference - it may contain path components"""
# first two path tokens are can be removed as they are
# refs/heads or refs/tags or refs/remotes
tokens = self.path.split('/')
if len(tokens) < 3:
return self.path # could be refs/HEAD
return '/'.join(tokens[2:])
@classmethod
def iter_items(cls, repo, common_path=None):
"""Equivalent to SymbolicReference.iter_items, but will return non-detached
references as well."""
return cls._iter_items(repo, common_path)
#}END interface
#{ Remote Interface
@property
@require_remote_ref_path
def remote_name(self):
"""
:return:
Name of the remote we are a reference of, such as 'origin' for a reference
named 'origin/master'"""
tokens = self.path.split('/')
# /refs/remotes/<remote name>/<branch_name>
return tokens[2]
@property
@require_remote_ref_path
def remote_head(self):
""":return: Name of the remote head itself, i.e. master.
:note: The returned name is usually not qualified enough to uniquely identify
a branch"""
tokens = self.path.split('/')
return '/'.join(tokens[3:])
#} END remote interface
@@ -0,0 +1,52 @@
import os
from git.util import join_path
import os.path as osp
from .head import Head
__all__ = ["RemoteReference"]
class RemoteReference(Head):
"""Represents a reference pointing to a remote head."""
_common_path_default = Head._remote_common_path_default
@classmethod
def iter_items(cls, repo, common_path=None, remote=None):
"""Iterate remote references, and if given, constrain them to the given remote"""
common_path = common_path or cls._common_path_default
if remote is not None:
common_path = join_path(common_path, str(remote))
# END handle remote constraint
return super(RemoteReference, cls).iter_items(repo, common_path)
@classmethod
def delete(cls, repo, *refs, **kwargs):
"""Delete the given remote references
:note:
kwargs are given for comparability with the base class method as we
should not narrow the signature."""
repo.git.branch("-d", "-r", *refs)
# the official deletion method will ignore remote symbolic refs - these
# are generally ignored in the refs/ folder. We don't though
# and delete remainders manually
for ref in refs:
try:
os.remove(osp.join(repo.common_dir, ref.path))
except OSError:
pass
try:
os.remove(osp.join(repo.git_dir, ref.path))
except OSError:
pass
# END for each ref
@classmethod
def create(cls, *args, **kwargs):
"""Used to disable this method"""
raise TypeError("Cannot explicitly create remote references")
@@ -0,0 +1,679 @@
import os
from git.compat import (
string_types,
defenc
)
from git.objects import Object, Commit
from git.util import (
join_path,
join_path_native,
to_native_path_linux,
assure_directory_exists,
hex_to_bin,
LockedFD
)
from gitdb.exc import (
BadObject,
BadName
)
import os.path as osp
from .log import RefLog
__all__ = ["SymbolicReference"]
def _git_dir(repo, path):
""" Find the git dir that's appropriate for the path"""
name = "%s" % (path,)
if name in ['HEAD', 'ORIG_HEAD', 'FETCH_HEAD', 'index', 'logs']:
return repo.git_dir
return repo.common_dir
class SymbolicReference(object):
"""Represents a special case of a reference such that this reference is symbolic.
It does not point to a specific commit, but to another Head, which itself
specifies a commit.
A typical example for a symbolic reference is HEAD."""
__slots__ = ("repo", "path")
_resolve_ref_on_create = False
_points_to_commits_only = True
_common_path_default = ""
_remote_common_path_default = "refs/remotes"
_id_attribute_ = "name"
def __init__(self, repo, path):
self.repo = repo
self.path = path
def __str__(self):
return self.path
def __repr__(self):
return '<git.%s "%s">' % (self.__class__.__name__, self.path)
def __eq__(self, other):
if hasattr(other, 'path'):
return self.path == other.path
return False
def __ne__(self, other):
return not (self == other)
def __hash__(self):
return hash(self.path)
@property
def name(self):
"""
:return:
In case of symbolic references, the shortest assumable name
is the path itself."""
return self.path
@property
def abspath(self):
return join_path_native(_git_dir(self.repo, self.path), self.path)
@classmethod
def _get_packed_refs_path(cls, repo):
return osp.join(repo.common_dir, 'packed-refs')
@classmethod
def _iter_packed_refs(cls, repo):
"""Returns an iterator yielding pairs of sha1/path pairs (as bytes) for the corresponding refs.
:note: The packed refs file will be kept open as long as we iterate"""
try:
with open(cls._get_packed_refs_path(repo), 'rt') as fp:
for line in fp:
line = line.strip()
if not line:
continue
if line.startswith('#'):
# "# pack-refs with: peeled fully-peeled sorted"
# the git source code shows "peeled",
# "fully-peeled" and "sorted" as the keywords
# that can go on this line, as per comments in git file
# refs/packed-backend.c
# I looked at master on 2017-10-11,
# commit 111ef79afe, after tag v2.15.0-rc1
# from repo https://github.com/git/git.git
if line.startswith('# pack-refs with:') and 'peeled' not in line:
raise TypeError("PackingType of packed-Refs not understood: %r" % line)
# END abort if we do not understand the packing scheme
continue
# END parse comment
# skip dereferenced tag object entries - previous line was actual
# tag reference for it
if line[0] == '^':
continue
yield tuple(line.split(' ', 1))
# END for each line
except (OSError, IOError):
return
# END no packed-refs file handling
# NOTE: Had try-finally block around here to close the fp,
# but some python version wouldn't allow yields within that.
# I believe files are closing themselves on destruction, so it is
# alright.
@classmethod
def dereference_recursive(cls, repo, ref_path):
"""
:return: hexsha stored in the reference at the given ref_path, recursively dereferencing all
intermediate references as required
:param repo: the repository containing the reference at ref_path"""
while True:
hexsha, ref_path = cls._get_ref_info(repo, ref_path)
if hexsha is not None:
return hexsha
# END recursive dereferencing
@classmethod
def _get_ref_info_helper(cls, repo, ref_path):
"""Return: (str(sha), str(target_ref_path)) if available, the sha the file at
rela_path points to, or None. target_ref_path is the reference we
point to, or None"""
tokens = None
repodir = _git_dir(repo, ref_path)
try:
with open(osp.join(repodir, ref_path), 'rt') as fp:
value = fp.read().rstrip()
# Don't only split on spaces, but on whitespace, which allows to parse lines like
# 60b64ef992065e2600bfef6187a97f92398a9144 branch 'master' of git-server:/path/to/repo
tokens = value.split()
assert(len(tokens) != 0)
except (OSError, IOError):
# Probably we are just packed, find our entry in the packed refs file
# NOTE: We are not a symbolic ref if we are in a packed file, as these
# are excluded explicitly
for sha, path in cls._iter_packed_refs(repo):
if path != ref_path:
continue
# sha will be used
tokens = sha, path
break
# END for each packed ref
# END handle packed refs
if tokens is None:
raise ValueError("Reference at %r does not exist" % ref_path)
# is it a reference ?
if tokens[0] == 'ref:':
return (None, tokens[1])
# its a commit
if repo.re_hexsha_only.match(tokens[0]):
return (tokens[0], None)
raise ValueError("Failed to parse reference information from %r" % ref_path)
@classmethod
def _get_ref_info(cls, repo, ref_path):
"""Return: (str(sha), str(target_ref_path)) if available, the sha the file at
rela_path points to, or None. target_ref_path is the reference we
point to, or None"""
return cls._get_ref_info_helper(repo, ref_path)
def _get_object(self):
"""
:return:
The object our ref currently refers to. Refs can be cached, they will
always point to the actual object as it gets re-created on each query"""
# have to be dynamic here as we may be a tag which can point to anything
# Our path will be resolved to the hexsha which will be used accordingly
return Object.new_from_sha(self.repo, hex_to_bin(self.dereference_recursive(self.repo, self.path)))
def _get_commit(self):
"""
:return:
Commit object we point to, works for detached and non-detached
SymbolicReferences. The symbolic reference will be dereferenced recursively."""
obj = self._get_object()
if obj.type == 'tag':
obj = obj.object
# END dereference tag
if obj.type != Commit.type:
raise TypeError("Symbolic Reference pointed to object %r, commit was required" % obj)
# END handle type
return obj
def set_commit(self, commit, logmsg=None):
"""As set_object, but restricts the type of object to be a Commit
:raise ValueError: If commit is not a Commit object or doesn't point to
a commit
:return: self"""
# check the type - assume the best if it is a base-string
invalid_type = False
if isinstance(commit, Object):
invalid_type = commit.type != Commit.type
elif isinstance(commit, SymbolicReference):
invalid_type = commit.object.type != Commit.type
else:
try:
invalid_type = self.repo.rev_parse(commit).type != Commit.type
except (BadObject, BadName):
raise ValueError("Invalid object: %s" % commit)
# END handle exception
# END verify type
if invalid_type:
raise ValueError("Need commit, got %r" % commit)
# END handle raise
# we leave strings to the rev-parse method below
self.set_object(commit, logmsg)
return self
def set_object(self, object, logmsg=None): # @ReservedAssignment
"""Set the object we point to, possibly dereference our symbolic reference first.
If the reference does not exist, it will be created
:param object: a refspec, a SymbolicReference or an Object instance. SymbolicReferences
will be dereferenced beforehand to obtain the object they point to
:param logmsg: If not None, the message will be used in the reflog entry to be
written. Otherwise the reflog is not altered
:note: plain SymbolicReferences may not actually point to objects by convention
:return: self"""
if isinstance(object, SymbolicReference):
object = object.object # @ReservedAssignment
# END resolve references
is_detached = True
try:
is_detached = self.is_detached
except ValueError:
pass
# END handle non-existing ones
if is_detached:
return self.set_reference(object, logmsg)
# set the commit on our reference
return self._get_reference().set_object(object, logmsg)
commit = property(_get_commit, set_commit, doc="Query or set commits directly")
object = property(_get_object, set_object, doc="Return the object our ref currently refers to")
def _get_reference(self):
""":return: Reference Object we point to
:raise TypeError: If this symbolic reference is detached, hence it doesn't point
to a reference, but to a commit"""
sha, target_ref_path = self._get_ref_info(self.repo, self.path)
if target_ref_path is None:
raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha))
return self.from_path(self.repo, target_ref_path)
def set_reference(self, ref, logmsg=None):
"""Set ourselves to the given ref. It will stay a symbol if the ref is a Reference.
Otherwise an Object, given as Object instance or refspec, is assumed and if valid,
will be set which effectively detaches the refererence if it was a purely
symbolic one.
:param ref: SymbolicReference instance, Object instance or refspec string
Only if the ref is a SymbolicRef instance, we will point to it. Everything
else is dereferenced to obtain the actual object.
:param logmsg: If set to a string, the message will be used in the reflog.
Otherwise, a reflog entry is not written for the changed reference.
The previous commit of the entry will be the commit we point to now.
See also: log_append()
:return: self
:note: This symbolic reference will not be dereferenced. For that, see
``set_object(...)``"""
write_value = None
obj = None
if isinstance(ref, SymbolicReference):
write_value = "ref: %s" % ref.path
elif isinstance(ref, Object):
obj = ref
write_value = ref.hexsha
elif isinstance(ref, string_types):
try:
obj = self.repo.rev_parse(ref + "^{}") # optionally deref tags
write_value = obj.hexsha
except (BadObject, BadName):
raise ValueError("Could not extract object from %s" % ref)
# END end try string
else:
raise ValueError("Unrecognized Value: %r" % ref)
# END try commit attribute
# typecheck
if obj is not None and self._points_to_commits_only and obj.type != Commit.type:
raise TypeError("Require commit, got %r" % obj)
# END verify type
oldbinsha = None
if logmsg is not None:
try:
oldbinsha = self.commit.binsha
except ValueError:
oldbinsha = Commit.NULL_BIN_SHA
# END handle non-existing
# END retrieve old hexsha
fpath = self.abspath
assure_directory_exists(fpath, is_file=True)
lfd = LockedFD(fpath)
fd = lfd.open(write=True, stream=True)
ok = True
try:
fd.write(write_value.encode('ascii') + b'\n')
lfd.commit()
ok = True
finally:
if not ok:
lfd.rollback()
# Adjust the reflog
if logmsg is not None:
self.log_append(oldbinsha, logmsg)
return self
# aliased reference
reference = property(_get_reference, set_reference, doc="Returns the Reference we point to")
ref = reference
def is_valid(self):
"""
:return:
True if the reference is valid, hence it can be read and points to
a valid object or reference."""
try:
self.object
except (OSError, ValueError):
return False
else:
return True
@property
def is_detached(self):
"""
:return:
True if we are a detached reference, hence we point to a specific commit
instead to another reference"""
try:
self.ref
return False
except TypeError:
return True
def log(self):
"""
:return: RefLog for this reference. Its last entry reflects the latest change
applied to this reference
.. note:: As the log is parsed every time, its recommended to cache it for use
instead of calling this method repeatedly. It should be considered read-only."""
return RefLog.from_file(RefLog.path(self))
def log_append(self, oldbinsha, message, newbinsha=None):
"""Append a logentry to the logfile of this ref
:param oldbinsha: binary sha this ref used to point to
:param message: A message describing the change
:param newbinsha: The sha the ref points to now. If None, our current commit sha
will be used
:return: added RefLogEntry instance"""
# NOTE: we use the committer of the currently active commit - this should be
# correct to allow overriding the committer on a per-commit level.
# See https://github.com/gitpython-developers/GitPython/pull/146
try:
committer_or_reader = self.commit.committer
except ValueError:
committer_or_reader = self.repo.config_reader()
# end handle newly cloned repositories
return RefLog.append_entry(committer_or_reader, RefLog.path(self), oldbinsha,
(newbinsha is None and self.commit.binsha) or newbinsha,
message)
def log_entry(self, index):
""":return: RefLogEntry at the given index
:param index: python list compatible positive or negative index
.. note:: This method must read part of the reflog during execution, hence
it should be used sparringly, or only if you need just one index.
In that case, it will be faster than the ``log()`` method"""
return RefLog.entry_at(RefLog.path(self), index)
@classmethod
def to_full_path(cls, path):
"""
:return: string with a full repository-relative path which can be used to initialize
a Reference instance, for instance by using ``Reference.from_path``"""
if isinstance(path, SymbolicReference):
path = path.path
full_ref_path = path
if not cls._common_path_default:
return full_ref_path
if not path.startswith(cls._common_path_default + "/"):
full_ref_path = '%s/%s' % (cls._common_path_default, path)
return full_ref_path
@classmethod
def delete(cls, repo, path):
"""Delete the reference at the given path
:param repo:
Repository to delete the reference from
:param path:
Short or full path pointing to the reference, i.e. refs/myreference
or just "myreference", hence 'refs/' is implied.
Alternatively the symbolic reference to be deleted"""
full_ref_path = cls.to_full_path(path)
abs_path = osp.join(repo.common_dir, full_ref_path)
if osp.exists(abs_path):
os.remove(abs_path)
else:
# check packed refs
pack_file_path = cls._get_packed_refs_path(repo)
try:
with open(pack_file_path, 'rb') as reader:
new_lines = []
made_change = False
dropped_last_line = False
for line in reader:
# keep line if it is a comment or if the ref to delete is not
# in the line
# If we deleted the last line and this one is a tag-reference object,
# we drop it as well
line = line.decode(defenc)
if (line.startswith('#') or full_ref_path not in line) and \
(not dropped_last_line or dropped_last_line and not line.startswith('^')):
new_lines.append(line)
dropped_last_line = False
continue
# END skip comments and lines without our path
# drop this line
made_change = True
dropped_last_line = True
# write the new lines
if made_change:
# write-binary is required, otherwise windows will
# open the file in text mode and change LF to CRLF !
with open(pack_file_path, 'wb') as fd:
fd.writelines(l.encode(defenc) for l in new_lines)
except (OSError, IOError):
pass # it didn't exist at all
# delete the reflog
reflog_path = RefLog.path(cls(repo, full_ref_path))
if osp.isfile(reflog_path):
os.remove(reflog_path)
# END remove reflog
@classmethod
def _create(cls, repo, path, resolve, reference, force, logmsg=None):
"""internal method used to create a new symbolic reference.
If resolve is False, the reference will be taken as is, creating
a proper symbolic reference. Otherwise it will be resolved to the
corresponding object and a detached symbolic reference will be created
instead"""
git_dir = _git_dir(repo, path)
full_ref_path = cls.to_full_path(path)
abs_ref_path = osp.join(git_dir, full_ref_path)
# figure out target data
target = reference
if resolve:
target = repo.rev_parse(str(reference))
if not force and osp.isfile(abs_ref_path):
target_data = str(target)
if isinstance(target, SymbolicReference):
target_data = target.path
if not resolve:
target_data = "ref: " + target_data
with open(abs_ref_path, 'rb') as fd:
existing_data = fd.read().decode(defenc).strip()
if existing_data != target_data:
raise OSError("Reference at %r does already exist, pointing to %r, requested was %r" %
(full_ref_path, existing_data, target_data))
# END no force handling
ref = cls(repo, full_ref_path)
ref.set_reference(target, logmsg)
return ref
@classmethod
def create(cls, repo, path, reference='HEAD', force=False, logmsg=None):
"""Create a new symbolic reference, hence a reference pointing to another reference.
:param repo:
Repository to create the reference in
:param path:
full path at which the new symbolic reference is supposed to be
created at, i.e. "NEW_HEAD" or "symrefs/my_new_symref"
:param reference:
The reference to which the new symbolic reference should point to.
If it is a commit'ish, the symbolic ref will be detached.
:param force:
if True, force creation even if a symbolic reference with that name already exists.
Raise OSError otherwise
:param logmsg:
If not None, the message to append to the reflog. Otherwise no reflog
entry is written.
:return: Newly created symbolic Reference
:raise OSError:
If a (Symbolic)Reference with the same name but different contents
already exists.
:note: This does not alter the current HEAD, index or Working Tree"""
return cls._create(repo, path, cls._resolve_ref_on_create, reference, force, logmsg)
def rename(self, new_path, force=False):
"""Rename self to a new path
:param new_path:
Either a simple name or a full path, i.e. new_name or features/new_name.
The prefix refs/ is implied for references and will be set as needed.
In case this is a symbolic ref, there is no implied prefix
:param force:
If True, the rename will succeed even if a head with the target name
already exists. It will be overwritten in that case
:return: self
:raise OSError: In case a file at path but a different contents already exists """
new_path = self.to_full_path(new_path)
if self.path == new_path:
return self
new_abs_path = osp.join(_git_dir(self.repo, new_path), new_path)
cur_abs_path = osp.join(_git_dir(self.repo, self.path), self.path)
if osp.isfile(new_abs_path):
if not force:
# if they point to the same file, its not an error
with open(new_abs_path, 'rb') as fd1:
f1 = fd1.read().strip()
with open(cur_abs_path, 'rb') as fd2:
f2 = fd2.read().strip()
if f1 != f2:
raise OSError("File at path %r already exists" % new_abs_path)
# else: we could remove ourselves and use the otherone, but
# but clarity we just continue as usual
# END not force handling
os.remove(new_abs_path)
# END handle existing target file
dname = osp.dirname(new_abs_path)
if not osp.isdir(dname):
os.makedirs(dname)
# END create directory
os.rename(cur_abs_path, new_abs_path)
self.path = new_path
return self
@classmethod
def _iter_items(cls, repo, common_path=None):
if common_path is None:
common_path = cls._common_path_default
rela_paths = set()
# walk loose refs
# Currently we do not follow links
for root, dirs, files in os.walk(join_path_native(repo.common_dir, common_path)):
if 'refs' not in root.split(os.sep): # skip non-refs subfolders
refs_id = [d for d in dirs if d == 'refs']
if refs_id:
dirs[0:] = ['refs']
# END prune non-refs folders
for f in files:
if f == 'packed-refs':
continue
abs_path = to_native_path_linux(join_path(root, f))
rela_paths.add(abs_path.replace(to_native_path_linux(repo.common_dir) + '/', ""))
# END for each file in root directory
# END for each directory to walk
# read packed refs
for sha, rela_path in cls._iter_packed_refs(repo): # @UnusedVariable
if rela_path.startswith(common_path):
rela_paths.add(rela_path)
# END relative path matches common path
# END packed refs reading
# return paths in sorted order
for path in sorted(rela_paths):
try:
yield cls.from_path(repo, path)
except ValueError:
continue
# END for each sorted relative refpath
@classmethod
def iter_items(cls, repo, common_path=None):
"""Find all refs in the repository
:param repo: is the Repo
:param common_path:
Optional keyword argument to the path which is to be shared by all
returned Ref objects.
Defaults to class specific portion if None assuring that only
refs suitable for the actual class are returned.
:return:
git.SymbolicReference[], each of them is guaranteed to be a symbolic
ref which is not detached and pointing to a valid ref
List is lexicographically sorted
The returned objects represent actual subclasses, such as Head or TagReference"""
return (r for r in cls._iter_items(repo, common_path) if r.__class__ == SymbolicReference or not r.is_detached)
@classmethod
def from_path(cls, repo, path):
"""
:param path: full .git-directory-relative path name to the Reference to instantiate
:note: use to_full_path() if you only have a partial path of a known Reference Type
:return:
Instance of type Reference, Head, or Tag
depending on the given path"""
if not path:
raise ValueError("Cannot create Reference from %r" % path)
# Names like HEAD are inserted after the refs module is imported - we have an import dependency
# cycle and don't want to import these names in-function
from . import HEAD, Head, RemoteReference, TagReference, Reference
for ref_type in (HEAD, Head, RemoteReference, TagReference, Reference, SymbolicReference):
try:
instance = ref_type(repo, path)
if instance.__class__ == SymbolicReference and instance.is_detached:
raise ValueError("SymbolRef was detached, we drop it")
return instance
except ValueError:
pass
# END exception handling
# END for each type to try
raise ValueError("Could not find reference type suitable to handle path %r" % path)
def is_remote(self):
""":return: True if this symbolic reference points to a remote branch"""
return self.path.startswith(self._remote_common_path_default + "/")
@@ -0,0 +1,93 @@
from .reference import Reference
__all__ = ["TagReference", "Tag"]
class TagReference(Reference):
"""Class representing a lightweight tag reference which either points to a commit
,a tag object or any other object. In the latter case additional information,
like the signature or the tag-creator, is available.
This tag object will always point to a commit object, but may carry additional
information in a tag object::
tagref = TagReference.list_items(repo)[0]
print(tagref.commit.message)
if tagref.tag is not None:
print(tagref.tag.message)"""
__slots__ = ()
_common_path_default = "refs/tags"
@property
def commit(self):
""":return: Commit object the tag ref points to
:raise ValueError: if the tag points to a tree or blob"""
obj = self.object
while obj.type != 'commit':
if obj.type == "tag":
# it is a tag object which carries the commit as an object - we can point to anything
obj = obj.object
else:
raise ValueError(("Cannot resolve commit as tag %s points to a %s object - " +
"use the `.object` property instead to access it") % (self, obj.type))
return obj
@property
def tag(self):
"""
:return: Tag object this tag ref points to or None in case
we are a light weight tag"""
obj = self.object
if obj.type == "tag":
return obj
return None
# make object read-only
# It should be reasonably hard to adjust an existing tag
object = property(Reference._get_object)
@classmethod
def create(cls, repo, path, ref='HEAD', message=None, force=False, **kwargs):
"""Create a new tag reference.
:param path:
The name of the tag, i.e. 1.0 or releases/1.0.
The prefix refs/tags is implied
:param ref:
A reference to the object you want to tag. It can be a commit, tree or
blob.
:param message:
If not None, the message will be used in your tag object. This will also
create an additional tag object that allows to obtain that information, i.e.::
tagref.tag.message
:param force:
If True, to force creation of a tag even though that tag already exists.
:param kwargs:
Additional keyword arguments to be passed to git-tag
:return: A new TagReference"""
args = (path, ref)
if message:
kwargs['m'] = message
if force:
kwargs['f'] = True
repo.git.tag(*args, **kwargs)
return TagReference(repo, "%s/%s" % (cls._common_path_default, path))
@classmethod
def delete(cls, repo, *tags):
"""Delete the given existing tag or tags"""
repo.git.tag("-d", *tags)
# provide an alias
Tag = TagReference
@@ -0,0 +1,876 @@
# remote.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
# Module implementing a remote object allowing easy access to git remotes
import logging
import re
from git.cmd import handle_process_output, Git
from git.compat import (defenc, force_text, is_win)
from git.exc import GitCommandError
from git.util import (
LazyMixin,
Iterable,
IterableList,
RemoteProgress,
CallableRemoteProgress
)
from git.util import (
join_path,
)
import os.path as osp
from .config import (
SectionConstraint,
cp,
)
from .refs import (
Head,
Reference,
RemoteReference,
SymbolicReference,
TagReference
)
log = logging.getLogger('git.remote')
log.addHandler(logging.NullHandler())
__all__ = ('RemoteProgress', 'PushInfo', 'FetchInfo', 'Remote')
#{ Utilities
def add_progress(kwargs, git, progress):
"""Add the --progress flag to the given kwargs dict if supported by the
git command. If the actual progress in the given progress instance is not
given, we do not request any progress
:return: possibly altered kwargs"""
if progress is not None:
v = git.version_info[:2]
if v >= (1, 7):
kwargs['progress'] = True
# END handle --progress
# END handle progress
return kwargs
#} END utilities
def to_progress_instance(progress):
"""Given the 'progress' return a suitable object derived from
RemoteProgress().
"""
# new API only needs progress as a function
if callable(progress):
return CallableRemoteProgress(progress)
# where None is passed create a parser that eats the progress
elif progress is None:
return RemoteProgress()
# assume its the old API with an instance of RemoteProgress.
else:
return progress
class PushInfo(object):
"""
Carries information about the result of a push operation of a single head::
info = remote.push()[0]
info.flags # bitflags providing more information about the result
info.local_ref # Reference pointing to the local reference that was pushed
# It is None if the ref was deleted.
info.remote_ref_string # path to the remote reference located on the remote side
info.remote_ref # Remote Reference on the local side corresponding to
# the remote_ref_string. It can be a TagReference as well.
info.old_commit # commit at which the remote_ref was standing before we pushed
# it to local_ref.commit. Will be None if an error was indicated
info.summary # summary line providing human readable english text about the push
"""
__slots__ = ('local_ref', 'remote_ref_string', 'flags', '_old_commit_sha', '_remote', 'summary')
NEW_TAG, NEW_HEAD, NO_MATCH, REJECTED, REMOTE_REJECTED, REMOTE_FAILURE, DELETED, \
FORCED_UPDATE, FAST_FORWARD, UP_TO_DATE, ERROR = [1 << x for x in range(11)]
_flag_map = {'X': NO_MATCH,
'-': DELETED,
'*': 0,
'+': FORCED_UPDATE,
' ': FAST_FORWARD,
'=': UP_TO_DATE,
'!': ERROR}
def __init__(self, flags, local_ref, remote_ref_string, remote, old_commit=None,
summary=''):
""" Initialize a new instance """
self.flags = flags
self.local_ref = local_ref
self.remote_ref_string = remote_ref_string
self._remote = remote
self._old_commit_sha = old_commit
self.summary = summary
@property
def old_commit(self):
return self._old_commit_sha and self._remote.repo.commit(self._old_commit_sha) or None
@property
def remote_ref(self):
"""
:return:
Remote Reference or TagReference in the local repository corresponding
to the remote_ref_string kept in this instance."""
# translate heads to a local remote, tags stay as they are
if self.remote_ref_string.startswith("refs/tags"):
return TagReference(self._remote.repo, self.remote_ref_string)
elif self.remote_ref_string.startswith("refs/heads"):
remote_ref = Reference(self._remote.repo, self.remote_ref_string)
return RemoteReference(self._remote.repo, "refs/remotes/%s/%s" % (str(self._remote), remote_ref.name))
else:
raise ValueError("Could not handle remote ref: %r" % self.remote_ref_string)
# END
@classmethod
def _from_line(cls, remote, line):
"""Create a new PushInfo instance as parsed from line which is expected to be like
refs/heads/master:refs/heads/master 05d2687..1d0568e as bytes"""
control_character, from_to, summary = line.split('\t', 3)
flags = 0
# control character handling
try:
flags |= cls._flag_map[control_character]
except KeyError:
raise ValueError("Control character %r unknown as parsed from line %r" % (control_character, line))
# END handle control character
# from_to handling
from_ref_string, to_ref_string = from_to.split(':')
if flags & cls.DELETED:
from_ref = None
else:
from_ref = Reference.from_path(remote.repo, from_ref_string)
# commit handling, could be message or commit info
old_commit = None
if summary.startswith('['):
if "[rejected]" in summary:
flags |= cls.REJECTED
elif "[remote rejected]" in summary:
flags |= cls.REMOTE_REJECTED
elif "[remote failure]" in summary:
flags |= cls.REMOTE_FAILURE
elif "[no match]" in summary:
flags |= cls.ERROR
elif "[new tag]" in summary:
flags |= cls.NEW_TAG
elif "[new branch]" in summary:
flags |= cls.NEW_HEAD
# uptodate encoded in control character
else:
# fast-forward or forced update - was encoded in control character,
# but we parse the old and new commit
split_token = "..."
if control_character == " ":
split_token = ".."
old_sha, new_sha = summary.split(' ')[0].split(split_token) # @UnusedVariable
# have to use constructor here as the sha usually is abbreviated
old_commit = old_sha
# END message handling
return PushInfo(flags, from_ref, to_ref_string, remote, old_commit, summary)
class FetchInfo(object):
"""
Carries information about the results of a fetch operation of a single head::
info = remote.fetch()[0]
info.ref # Symbolic Reference or RemoteReference to the changed
# remote head or FETCH_HEAD
info.flags # additional flags to be & with enumeration members,
# i.e. info.flags & info.REJECTED
# is 0 if ref is SymbolicReference
info.note # additional notes given by git-fetch intended for the user
info.old_commit # if info.flags & info.FORCED_UPDATE|info.FAST_FORWARD,
# field is set to the previous location of ref, otherwise None
info.remote_ref_path # The path from which we fetched on the remote. It's the remote's version of our info.ref
"""
__slots__ = ('ref', 'old_commit', 'flags', 'note', 'remote_ref_path')
NEW_TAG, NEW_HEAD, HEAD_UPTODATE, TAG_UPDATE, REJECTED, FORCED_UPDATE, \
FAST_FORWARD, ERROR = [1 << x for x in range(8)]
_re_fetch_result = re.compile(r'^\s*(.) (\[?[\w\s\.$@]+\]?)\s+(.+) -> ([^\s]+)( \(.*\)?$)?')
_flag_map = {
'!': ERROR,
'+': FORCED_UPDATE,
'*': 0,
'=': HEAD_UPTODATE,
' ': FAST_FORWARD,
'-': TAG_UPDATE,
}
@classmethod
def refresh(cls):
"""This gets called by the refresh function (see the top level
__init__).
"""
# clear the old values in _flag_map
try:
del cls._flag_map["t"]
except KeyError:
pass
try:
del cls._flag_map["-"]
except KeyError:
pass
# set the value given the git version
if Git().version_info[:2] >= (2, 10):
cls._flag_map["t"] = cls.TAG_UPDATE
else:
cls._flag_map["-"] = cls.TAG_UPDATE
return True
def __init__(self, ref, flags, note='', old_commit=None, remote_ref_path=None):
"""
Initialize a new instance
"""
self.ref = ref
self.flags = flags
self.note = note
self.old_commit = old_commit
self.remote_ref_path = remote_ref_path
def __str__(self):
return self.name
@property
def name(self):
""":return: Name of our remote ref"""
return self.ref.name
@property
def commit(self):
""":return: Commit of our remote ref"""
return self.ref.commit
@classmethod
def _from_line(cls, repo, line, fetch_line):
"""Parse information from the given line as returned by git-fetch -v
and return a new FetchInfo object representing this information.
We can handle a line as follows
"%c %-*s %-*s -> %s%s"
Where c is either ' ', !, +, -, *, or =
! means error
+ means success forcing update
- means a tag was updated
* means birth of new branch or tag
= means the head was up to date ( and not moved )
' ' means a fast-forward
fetch line is the corresponding line from FETCH_HEAD, like
acb0fa8b94ef421ad60c8507b634759a472cd56c not-for-merge branch '0.1.7RC' of /tmp/tmpya0vairemote_repo"""
match = cls._re_fetch_result.match(line)
if match is None:
raise ValueError("Failed to parse line: %r" % line)
# parse lines
control_character, operation, local_remote_ref, remote_local_ref, note = match.groups()
try:
new_hex_sha, fetch_operation, fetch_note = fetch_line.split("\t") # @UnusedVariable
ref_type_name, fetch_note = fetch_note.split(' ', 1)
except ValueError: # unpack error
raise ValueError("Failed to parse FETCH_HEAD line: %r" % fetch_line)
# parse flags from control_character
flags = 0
try:
flags |= cls._flag_map[control_character]
except KeyError:
raise ValueError("Control character %r unknown as parsed from line %r" % (control_character, line))
# END control char exception handling
# parse operation string for more info - makes no sense for symbolic refs, but we parse it anyway
old_commit = None
is_tag_operation = False
if 'rejected' in operation:
flags |= cls.REJECTED
if 'new tag' in operation:
flags |= cls.NEW_TAG
is_tag_operation = True
if 'tag update' in operation:
flags |= cls.TAG_UPDATE
is_tag_operation = True
if 'new branch' in operation:
flags |= cls.NEW_HEAD
if '...' in operation or '..' in operation:
split_token = '...'
if control_character == ' ':
split_token = split_token[:-1]
old_commit = repo.rev_parse(operation.split(split_token)[0])
# END handle refspec
# handle FETCH_HEAD and figure out ref type
# If we do not specify a target branch like master:refs/remotes/origin/master,
# the fetch result is stored in FETCH_HEAD which destroys the rule we usually
# have. In that case we use a symbolic reference which is detached
ref_type = None
if remote_local_ref == "FETCH_HEAD":
ref_type = SymbolicReference
elif ref_type_name == "tag" or is_tag_operation:
# the ref_type_name can be branch, whereas we are still seeing a tag operation. It happens during
# testing, which is based on actual git operations
ref_type = TagReference
elif ref_type_name in ("remote-tracking", "branch"):
# note: remote-tracking is just the first part of the 'remote-tracking branch' token.
# We don't parse it correctly, but its enough to know what to do, and its new in git 1.7something
ref_type = RemoteReference
elif '/' in ref_type_name:
# If the fetch spec look something like this '+refs/pull/*:refs/heads/pull/*', and is thus pretty
# much anything the user wants, we will have trouble to determine what's going on
# For now, we assume the local ref is a Head
ref_type = Head
else:
raise TypeError("Cannot handle reference type: %r" % ref_type_name)
# END handle ref type
# create ref instance
if ref_type is SymbolicReference:
remote_local_ref = ref_type(repo, "FETCH_HEAD")
else:
# determine prefix. Tags are usually pulled into refs/tags, they may have subdirectories.
# It is not clear sometimes where exactly the item is, unless we have an absolute path as indicated
# by the 'ref/' prefix. Otherwise even a tag could be in refs/remotes, which is when it will have the
# 'tags/' subdirectory in its path.
# We don't want to test for actual existence, but try to figure everything out analytically.
ref_path = None
remote_local_ref = remote_local_ref.strip()
if remote_local_ref.startswith(Reference._common_path_default + "/"):
# always use actual type if we get absolute paths
# Will always be the case if something is fetched outside of refs/remotes (if its not a tag)
ref_path = remote_local_ref
if ref_type is not TagReference and not \
remote_local_ref.startswith(RemoteReference._common_path_default + "/"):
ref_type = Reference
# END downgrade remote reference
elif ref_type is TagReference and 'tags/' in remote_local_ref:
# even though its a tag, it is located in refs/remotes
ref_path = join_path(RemoteReference._common_path_default, remote_local_ref)
else:
ref_path = join_path(ref_type._common_path_default, remote_local_ref)
# END obtain refpath
# even though the path could be within the git conventions, we make
# sure we respect whatever the user wanted, and disabled path checking
remote_local_ref = ref_type(repo, ref_path, check_path=False)
# END create ref instance
note = (note and note.strip()) or ''
return cls(remote_local_ref, flags, note, old_commit, local_remote_ref)
class Remote(LazyMixin, Iterable):
"""Provides easy read and write access to a git remote.
Everything not part of this interface is considered an option for the current
remote, allowing constructs like remote.pushurl to query the pushurl.
NOTE: When querying configuration, the configuration accessor will be cached
to speed up subsequent accesses."""
__slots__ = ("repo", "name", "_config_reader")
_id_attribute_ = "name"
def __init__(self, repo, name):
"""Initialize a remote instance
:param repo: The repository we are a remote of
:param name: the name of the remote, i.e. 'origin'"""
self.repo = repo
self.name = name
if is_win:
# some oddity: on windows, python 2.5, it for some reason does not realize
# that it has the config_writer property, but instead calls __getattr__
# which will not yield the expected results. 'pinging' the members
# with a dir call creates the config_writer property that we require
# ... bugs like these make me wonder whether python really wants to be used
# for production. It doesn't happen on linux though.
dir(self)
# END windows special handling
def __getattr__(self, attr):
"""Allows to call this instance like
remote.special( \\*args, \\*\\*kwargs) to call git-remote special self.name"""
if attr == "_config_reader":
return super(Remote, self).__getattr__(attr)
# sometimes, probably due to a bug in python itself, we are being called
# even though a slot of the same name exists
try:
return self._config_reader.get(attr)
except cp.NoOptionError:
return super(Remote, self).__getattr__(attr)
# END handle exception
def _config_section_name(self):
return 'remote "%s"' % self.name
def _set_cache_(self, attr):
if attr == "_config_reader":
# NOTE: This is cached as __getattr__ is overridden to return remote config values implicitly, such as
# in print(r.pushurl)
self._config_reader = SectionConstraint(self.repo.config_reader("repository"), self._config_section_name())
else:
super(Remote, self)._set_cache_(attr)
def __str__(self):
return self.name
def __repr__(self):
return '<git.%s "%s">' % (self.__class__.__name__, self.name)
def __eq__(self, other):
return self.name == other.name
def __ne__(self, other):
return not (self == other)
def __hash__(self):
return hash(self.name)
def exists(self):
"""
:return: True if this is a valid, existing remote.
Valid remotes have an entry in the repository's configuration"""
try:
self.config_reader.get('url')
return True
except cp.NoOptionError:
# we have the section at least ...
return True
except cp.NoSectionError:
return False
# end
@classmethod
def iter_items(cls, repo):
""":return: Iterator yielding Remote objects of the given repository"""
for section in repo.config_reader("repository").sections():
if not section.startswith('remote '):
continue
lbound = section.find('"')
rbound = section.rfind('"')
if lbound == -1 or rbound == -1:
raise ValueError("Remote-Section has invalid format: %r" % section)
yield Remote(repo, section[lbound + 1:rbound])
# END for each configuration section
def set_url(self, new_url, old_url=None, **kwargs):
"""Configure URLs on current remote (cf command git remote set_url)
This command manages URLs on the remote.
:param new_url: string being the URL to add as an extra remote URL
:param old_url: when set, replaces this URL with new_url for the remote
:return: self
"""
scmd = 'set-url'
kwargs['insert_kwargs_after'] = scmd
if old_url:
self.repo.git.remote(scmd, self.name, new_url, old_url, **kwargs)
else:
self.repo.git.remote(scmd, self.name, new_url, **kwargs)
return self
def add_url(self, url, **kwargs):
"""Adds a new url on current remote (special case of git remote set_url)
This command adds new URLs to a given remote, making it possible to have
multiple URLs for a single remote.
:param url: string being the URL to add as an extra remote URL
:return: self
"""
return self.set_url(url, add=True)
def delete_url(self, url, **kwargs):
"""Deletes a new url on current remote (special case of git remote set_url)
This command deletes new URLs to a given remote, making it possible to have
multiple URLs for a single remote.
:param url: string being the URL to delete from the remote
:return: self
"""
return self.set_url(url, delete=True)
@property
def urls(self):
""":return: Iterator yielding all configured URL targets on a remote as strings"""
try:
remote_details = self.repo.git.remote("get-url", "--all", self.name)
for line in remote_details.split('\n'):
yield line
except GitCommandError as ex:
## We are on git < 2.7 (i.e TravisCI as of Oct-2016),
# so `get-utl` command does not exist yet!
# see: https://github.com/gitpython-developers/GitPython/pull/528#issuecomment-252976319
# and: http://stackoverflow.com/a/32991784/548792
#
if 'Unknown subcommand: get-url' in str(ex):
try:
remote_details = self.repo.git.remote("show", self.name)
for line in remote_details.split('\n'):
if ' Push URL:' in line:
yield line.split(': ')[-1]
except GitCommandError as ex:
if any(msg in str(ex) for msg in ['correct access rights', 'cannot run ssh']):
# If ssh is not setup to access this repository, see issue 694
remote_details = self.repo.git.config('--get-all', 'remote.%s.url' % self.name)
for line in remote_details.split('\n'):
yield line
else:
raise ex
else:
raise ex
@property
def refs(self):
"""
:return:
IterableList of RemoteReference objects. It is prefixed, allowing
you to omit the remote path portion, i.e.::
remote.refs.master # yields RemoteReference('/refs/remotes/origin/master')"""
out_refs = IterableList(RemoteReference._id_attribute_, "%s/" % self.name)
out_refs.extend(RemoteReference.list_items(self.repo, remote=self.name))
return out_refs
@property
def stale_refs(self):
"""
:return:
IterableList RemoteReference objects that do not have a corresponding
head in the remote reference anymore as they have been deleted on the
remote side, but are still available locally.
The IterableList is prefixed, hence the 'origin' must be omitted. See
'refs' property for an example.
To make things more complicated, it can be possible for the list to include
other kinds of references, for example, tag references, if these are stale
as well. This is a fix for the issue described here:
https://github.com/gitpython-developers/GitPython/issues/260
"""
out_refs = IterableList(RemoteReference._id_attribute_, "%s/" % self.name)
for line in self.repo.git.remote("prune", "--dry-run", self).splitlines()[2:]:
# expecting
# * [would prune] origin/new_branch
token = " * [would prune] "
if not line.startswith(token):
raise ValueError("Could not parse git-remote prune result: %r" % line)
ref_name = line.replace(token, "")
# sometimes, paths start with a full ref name, like refs/tags/foo, see #260
if ref_name.startswith(Reference._common_path_default + '/'):
out_refs.append(SymbolicReference.from_path(self.repo, ref_name))
else:
fqhn = "%s/%s" % (RemoteReference._common_path_default, ref_name)
out_refs.append(RemoteReference(self.repo, fqhn))
# end special case handling
# END for each line
return out_refs
@classmethod
def create(cls, repo, name, url, **kwargs):
"""Create a new remote to the given repository
:param repo: Repository instance that is to receive the new remote
:param name: Desired name of the remote
:param url: URL which corresponds to the remote's name
:param kwargs: Additional arguments to be passed to the git-remote add command
:return: New Remote instance
:raise GitCommandError: in case an origin with that name already exists"""
scmd = 'add'
kwargs['insert_kwargs_after'] = scmd
repo.git.remote(scmd, name, Git.polish_url(url), **kwargs)
return cls(repo, name)
# add is an alias
add = create
@classmethod
def remove(cls, repo, name):
"""Remove the remote with the given name
:return: the passed remote name to remove
"""
repo.git.remote("rm", name)
if isinstance(name, cls):
name._clear_cache()
return name
# alias
rm = remove
def rename(self, new_name):
"""Rename self to the given new_name
:return: self """
if self.name == new_name:
return self
self.repo.git.remote("rename", self.name, new_name)
self.name = new_name
self._clear_cache()
return self
def update(self, **kwargs):
"""Fetch all changes for this remote, including new branches which will
be forced in ( in case your local remote branch is not part the new remote branches
ancestry anymore ).
:param kwargs:
Additional arguments passed to git-remote update
:return: self """
scmd = 'update'
kwargs['insert_kwargs_after'] = scmd
self.repo.git.remote(scmd, self.name, **kwargs)
return self
def _get_fetch_info_from_stderr(self, proc, progress):
progress = to_progress_instance(progress)
# skip first line as it is some remote info we are not interested in
output = IterableList('name')
# lines which are no progress are fetch info lines
# this also waits for the command to finish
# Skip some progress lines that don't provide relevant information
fetch_info_lines = []
# Basically we want all fetch info lines which appear to be in regular form, and thus have a
# command character. Everything else we ignore,
cmds = set(FetchInfo._flag_map.keys())
progress_handler = progress.new_message_handler()
handle_process_output(proc, None, progress_handler, finalizer=None, decode_streams=False)
stderr_text = progress.error_lines and '\n'.join(progress.error_lines) or ''
proc.wait(stderr=stderr_text)
if stderr_text:
log.warning("Error lines received while fetching: %s", stderr_text)
for line in progress.other_lines:
line = force_text(line)
for cmd in cmds:
if len(line) > 1 and line[0] == ' ' and line[1] == cmd:
fetch_info_lines.append(line)
continue
# read head information
with open(osp.join(self.repo.common_dir, 'FETCH_HEAD'), 'rb') as fp:
fetch_head_info = [l.decode(defenc) for l in fp.readlines()]
l_fil = len(fetch_info_lines)
l_fhi = len(fetch_head_info)
if l_fil != l_fhi:
msg = "Fetch head lines do not match lines provided via progress information\n"
msg += "length of progress lines %i should be equal to lines in FETCH_HEAD file %i\n"
msg += "Will ignore extra progress lines or fetch head lines."
msg %= (l_fil, l_fhi)
log.debug(msg)
log.debug("info lines: " + str(fetch_info_lines))
log.debug("head info : " + str(fetch_head_info))
if l_fil < l_fhi:
fetch_head_info = fetch_head_info[:l_fil]
else:
fetch_info_lines = fetch_info_lines[:l_fhi]
# end truncate correct list
# end sanity check + sanitization
output.extend(FetchInfo._from_line(self.repo, err_line, fetch_line)
for err_line, fetch_line in zip(fetch_info_lines, fetch_head_info))
return output
def _get_push_info(self, proc, progress):
progress = to_progress_instance(progress)
# read progress information from stderr
# we hope stdout can hold all the data, it should ...
# read the lines manually as it will use carriage returns between the messages
# to override the previous one. This is why we read the bytes manually
progress_handler = progress.new_message_handler()
output = IterableList('name')
def stdout_handler(line):
try:
output.append(PushInfo._from_line(self, line))
except ValueError:
# If an error happens, additional info is given which we parse below.
pass
handle_process_output(proc, stdout_handler, progress_handler, finalizer=None, decode_streams=False)
stderr_text = progress.error_lines and '\n'.join(progress.error_lines) or ''
try:
proc.wait(stderr=stderr_text)
except Exception:
if not output:
raise
elif stderr_text:
log.warning("Error lines received while fetching: %s", stderr_text)
return output
def _assert_refspec(self):
"""Turns out we can't deal with remotes if the refspec is missing"""
config = self.config_reader
unset = 'placeholder'
try:
if config.get_value('fetch', default=unset) is unset:
msg = "Remote '%s' has no refspec set.\n"
msg += "You can set it as follows:"
msg += " 'git config --add \"remote.%s.fetch +refs/heads/*:refs/heads/*\"'."
raise AssertionError(msg % (self.name, self.name))
finally:
config.release()
def fetch(self, refspec=None, progress=None, **kwargs):
"""Fetch the latest changes for this remote
:param refspec:
A "refspec" is used by fetch and push to describe the mapping
between remote ref and local ref. They are combined with a colon in
the format <src>:<dst>, preceded by an optional plus sign, +.
For example: git fetch $URL refs/heads/master:refs/heads/origin means
"grab the master branch head from the $URL and store it as my origin
branch head". And git push $URL refs/heads/master:refs/heads/to-upstream
means "publish my master branch head as to-upstream branch at $URL".
See also git-push(1).
Taken from the git manual
Fetch supports multiple refspecs (as the
underlying git-fetch does) - supplying a list rather than a string
for 'refspec' will make use of this facility.
:param progress: See 'push' method
:param kwargs: Additional arguments to be passed to git-fetch
:return:
IterableList(FetchInfo, ...) list of FetchInfo instances providing detailed
information about the fetch results
:note:
As fetch does not provide progress information to non-ttys, we cannot make
it available here unfortunately as in the 'push' method."""
if refspec is None:
# No argument refspec, then ensure the repo's config has a fetch refspec.
self._assert_refspec()
kwargs = add_progress(kwargs, self.repo.git, progress)
if isinstance(refspec, list):
args = refspec
else:
args = [refspec]
proc = self.repo.git.fetch(self, *args, as_process=True, with_stdout=False,
universal_newlines=True, v=True, **kwargs)
res = self._get_fetch_info_from_stderr(proc, progress)
if hasattr(self.repo.odb, 'update_cache'):
self.repo.odb.update_cache()
return res
def pull(self, refspec=None, progress=None, **kwargs):
"""Pull changes from the given branch, being the same as a fetch followed
by a merge of branch with your local branch.
:param refspec: see 'fetch' method
:param progress: see 'push' method
:param kwargs: Additional arguments to be passed to git-pull
:return: Please see 'fetch' method """
if refspec is None:
# No argument refspec, then ensure the repo's config has a fetch refspec.
self._assert_refspec()
kwargs = add_progress(kwargs, self.repo.git, progress)
proc = self.repo.git.pull(self, refspec, with_stdout=False, as_process=True,
universal_newlines=True, v=True, **kwargs)
res = self._get_fetch_info_from_stderr(proc, progress)
if hasattr(self.repo.odb, 'update_cache'):
self.repo.odb.update_cache()
return res
def push(self, refspec=None, progress=None, **kwargs):
"""Push changes from source branch in refspec to target branch in refspec.
:param refspec: see 'fetch' method
:param progress:
Can take one of many value types:
* None to discard progress information
* A function (callable) that is called with the progress information.
Signature: ``progress(op_code, cur_count, max_count=None, message='')``.
`Click here <http://goo.gl/NPa7st>`_ for a description of all arguments
given to the function.
* An instance of a class derived from ``git.RemoteProgress`` that
overrides the ``update()`` function.
:note: No further progress information is returned after push returns.
:param kwargs: Additional arguments to be passed to git-push
:return:
IterableList(PushInfo, ...) iterable list of PushInfo instances, each
one informing about an individual head which had been updated on the remote
side.
If the push contains rejected heads, these will have the PushInfo.ERROR bit set
in their flags.
If the operation fails completely, the length of the returned IterableList will
be null."""
kwargs = add_progress(kwargs, self.repo.git, progress)
proc = self.repo.git.push(self, refspec, porcelain=True, as_process=True,
universal_newlines=True, **kwargs)
return self._get_push_info(proc, progress)
@property
def config_reader(self):
"""
:return:
GitConfigParser compatible object able to read options for only our remote.
Hence you may simple type config.get("pushurl") to obtain the information"""
return self._config_reader
def _clear_cache(self):
try:
del(self._config_reader)
except AttributeError:
pass
# END handle exception
@property
def config_writer(self):
"""
:return: GitConfigParser compatible object able to write options for this remote.
:note:
You can only own one writer at a time - delete it to release the
configuration file and make it usable by others.
To assure consistent results, you should only query options through the
writer. Once you are done writing, you are free to use the config reader
once again."""
writer = self.repo.config_writer()
# clear our cache to assure we re-read the possibly changed configuration
self._clear_cache()
return SectionConstraint(writer, self._config_section_name())
@@ -0,0 +1,4 @@
"""Initialize the Repo package"""
# flake8: noqa
from __future__ import absolute_import
from .base import *
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,344 @@
"""Package with general repository related functions"""
import os
import stat
from string import digits
from git.compat import xrange
from git.exc import WorkTreeRepositoryUnsupported
from git.objects import Object
from git.refs import SymbolicReference
from git.util import hex_to_bin, bin_to_hex, decygpath
from gitdb.exc import (
BadObject,
BadName,
)
import os.path as osp
from git.cmd import Git
__all__ = ('rev_parse', 'is_git_dir', 'touch', 'find_submodule_git_dir', 'name_to_object', 'short_to_long', 'deref_tag',
'to_commit', 'find_worktree_git_dir')
def touch(filename):
with open(filename, "ab"):
pass
return filename
def is_git_dir(d):
""" This is taken from the git setup.c:is_git_directory
function.
@throws WorkTreeRepositoryUnsupported if it sees a worktree directory. It's quite hacky to do that here,
but at least clearly indicates that we don't support it.
There is the unlikely danger to throw if we see directories which just look like a worktree dir,
but are none."""
if osp.isdir(d):
if osp.isdir(osp.join(d, 'objects')) and osp.isdir(osp.join(d, 'refs')):
headref = osp.join(d, 'HEAD')
return osp.isfile(headref) or \
(osp.islink(headref) and
os.readlink(headref).startswith('refs'))
elif (osp.isfile(osp.join(d, 'gitdir')) and
osp.isfile(osp.join(d, 'commondir')) and
osp.isfile(osp.join(d, 'gitfile'))):
raise WorkTreeRepositoryUnsupported(d)
return False
def find_worktree_git_dir(dotgit):
"""Search for a gitdir for this worktree."""
try:
statbuf = os.stat(dotgit)
except OSError:
return None
if not stat.S_ISREG(statbuf.st_mode):
return None
try:
lines = open(dotgit, 'r').readlines()
for key, value in [line.strip().split(': ') for line in lines]:
if key == 'gitdir':
return value
except ValueError:
pass
return None
def find_submodule_git_dir(d):
"""Search for a submodule repo."""
if is_git_dir(d):
return d
try:
with open(d) as fp:
content = fp.read().rstrip()
except (IOError, OSError):
# it's probably not a file
pass
else:
if content.startswith('gitdir: '):
path = content[8:]
if Git.is_cygwin():
## Cygwin creates submodules prefixed with `/cygdrive/...` suffixes.
path = decygpath(path)
if not osp.isabs(path):
path = osp.normpath(osp.join(osp.dirname(d), path))
return find_submodule_git_dir(path)
# end handle exception
return None
def short_to_long(odb, hexsha):
""":return: long hexadecimal sha1 from the given less-than-40 byte hexsha
or None if no candidate could be found.
:param hexsha: hexsha with less than 40 byte"""
try:
return bin_to_hex(odb.partial_to_complete_sha_hex(hexsha))
except BadObject:
return None
# END exception handling
def name_to_object(repo, name, return_ref=False):
"""
:return: object specified by the given name, hexshas ( short and long )
as well as references are supported
:param return_ref: if name specifies a reference, we will return the reference
instead of the object. Otherwise it will raise BadObject or BadName
"""
hexsha = None
# is it a hexsha ? Try the most common ones, which is 7 to 40
if repo.re_hexsha_shortened.match(name):
if len(name) != 40:
# find long sha for short sha
hexsha = short_to_long(repo.odb, name)
else:
hexsha = name
# END handle short shas
# END find sha if it matches
# if we couldn't find an object for what seemed to be a short hexsha
# try to find it as reference anyway, it could be named 'aaa' for instance
if hexsha is None:
for base in ('%s', 'refs/%s', 'refs/tags/%s', 'refs/heads/%s', 'refs/remotes/%s', 'refs/remotes/%s/HEAD'):
try:
hexsha = SymbolicReference.dereference_recursive(repo, base % name)
if return_ref:
return SymbolicReference(repo, base % name)
# END handle symbolic ref
break
except ValueError:
pass
# END for each base
# END handle hexsha
# didn't find any ref, this is an error
if return_ref:
raise BadObject("Couldn't find reference named %r" % name)
# END handle return ref
# tried everything ? fail
if hexsha is None:
raise BadName(name)
# END assert hexsha was found
return Object.new_from_sha(repo, hex_to_bin(hexsha))
def deref_tag(tag):
"""Recursively dereference a tag and return the resulting object"""
while True:
try:
tag = tag.object
except AttributeError:
break
# END dereference tag
return tag
def to_commit(obj):
"""Convert the given object to a commit if possible and return it"""
if obj.type == 'tag':
obj = deref_tag(obj)
if obj.type != "commit":
raise ValueError("Cannot convert object %r to type commit" % obj)
# END verify type
return obj
def rev_parse(repo, rev):
"""
:return: Object at the given revision, either Commit, Tag, Tree or Blob
:param rev: git-rev-parse compatible revision specification as string, please see
http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html
for details
:raise BadObject: if the given revision could not be found
:raise ValueError: If rev couldn't be parsed
:raise IndexError: If invalid reflog index is specified"""
# colon search mode ?
if rev.startswith(':/'):
# colon search mode
raise NotImplementedError("commit by message search ( regex )")
# END handle search
obj = None
ref = None
output_type = "commit"
start = 0
parsed_to = 0
lr = len(rev)
while start < lr:
if rev[start] not in "^~:@":
start += 1
continue
# END handle start
token = rev[start]
if obj is None:
# token is a rev name
if start == 0:
ref = repo.head.ref
else:
if token == '@':
ref = name_to_object(repo, rev[:start], return_ref=True)
else:
obj = name_to_object(repo, rev[:start])
# END handle token
# END handle refname
if ref is not None:
obj = ref.commit
# END handle ref
# END initialize obj on first token
start += 1
# try to parse {type}
if start < lr and rev[start] == '{':
end = rev.find('}', start)
if end == -1:
raise ValueError("Missing closing brace to define type in %s" % rev)
output_type = rev[start + 1:end] # exclude brace
# handle type
if output_type == 'commit':
pass # default
elif output_type == 'tree':
try:
obj = to_commit(obj).tree
except (AttributeError, ValueError):
pass # error raised later
# END exception handling
elif output_type in ('', 'blob'):
if obj.type == 'tag':
obj = deref_tag(obj)
else:
# cannot do anything for non-tags
pass
# END handle tag
elif token == '@':
# try single int
assert ref is not None, "Requre Reference to access reflog"
revlog_index = None
try:
# transform reversed index into the format of our revlog
revlog_index = -(int(output_type) + 1)
except ValueError:
# TODO: Try to parse the other date options, using parse_date
# maybe
raise NotImplementedError("Support for additional @{...} modes not implemented")
# END handle revlog index
try:
entry = ref.log_entry(revlog_index)
except IndexError:
raise IndexError("Invalid revlog index: %i" % revlog_index)
# END handle index out of bound
obj = Object.new_from_sha(repo, hex_to_bin(entry.newhexsha))
# make it pass the following checks
output_type = None
else:
raise ValueError("Invalid output type: %s ( in %s )" % (output_type, rev))
# END handle output type
# empty output types don't require any specific type, its just about dereferencing tags
if output_type and obj.type != output_type:
raise ValueError("Could not accommodate requested object type %r, got %s" % (output_type, obj.type))
# END verify output type
start = end + 1 # skip brace
parsed_to = start
continue
# END parse type
# try to parse a number
num = 0
if token != ":":
found_digit = False
while start < lr:
if rev[start] in digits:
num = num * 10 + int(rev[start])
start += 1
found_digit = True
else:
break
# END handle number
# END number parse loop
# no explicit number given, 1 is the default
# It could be 0 though
if not found_digit:
num = 1
# END set default num
# END number parsing only if non-blob mode
parsed_to = start
# handle hierarchy walk
try:
if token == "~":
obj = to_commit(obj)
for _ in xrange(num):
obj = obj.parents[0]
# END for each history item to walk
elif token == "^":
obj = to_commit(obj)
# must be n'th parent
if num:
obj = obj.parents[num - 1]
elif token == ":":
if obj.type != "tree":
obj = obj.tree
# END get tree type
obj = obj[rev[start:]]
parsed_to = lr
else:
raise ValueError("Invalid token: %r" % token)
# END end handle tag
except (IndexError, AttributeError):
raise BadName("Invalid revision spec '%s' - not enough parent commits to reach '%s%i'" % (rev, token, num))
# END exception handling
# END parse loop
# still no obj ? Its probably a simple name
if obj is None:
obj = name_to_object(repo, rev)
parsed_to = lr
# END handle simple name
if obj is None:
raise ValueError("Revision specifier could not be parsed: %s" % rev)
if parsed_to != lr:
raise ValueError("Didn't consume complete rev spec %s, consumed part: %s" % (rev, rev[:parsed_to]))
return obj
@@ -0,0 +1,5 @@
# __init__.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
+131
View File
@@ -0,0 +1,131 @@
634396b2f541a9f2d58b00be1a07f0c358b999b3 1 1 7
author Tom Preston-Werner
author-mail <tom@mojombo.com>
author-time 1191997100
author-tz -0700
committer Tom Preston-Werner
committer-mail <tom@mojombo.com>
committer-time 1191997100
committer-tz -0700
filename lib/grit.rb
summary initial grit setup
boundary
$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
634396b2f541a9f2d58b00be1a07f0c358b999b3 2 2
634396b2f541a9f2d58b00be1a07f0c358b999b3 3 3
# core
634396b2f541a9f2d58b00be1a07f0c358b999b3 4 4
634396b2f541a9f2d58b00be1a07f0c358b999b3 5 5
# stdlib
634396b2f541a9f2d58b00be1a07f0c358b999b3 6 6
634396b2f541a9f2d58b00be1a07f0c358b999b3 7 7
# internal requires
3b1930208a82457747d76729ae088e90edca4673 8 8 1
author Tom Preston-Werner
author-mail <tom@mojombo.com>
author-time 1192267241
author-tz -0700
committer Tom Preston-Werner
committer-mail <tom@mojombo.com>
committer-time 1192267241
committer-tz -0700
filename lib/grit.rb
summary big refactor to do lazy loading
require 'grit/lazy'
4c8124ffcf4039d292442eeccabdeca5af5c5017 8 9 1
author Tom Preston-Werner
author-mail <tom@mojombo.com>
author-time 1191999972
author-tz -0700
committer Tom Preston-Werner
committer-mail <tom@mojombo.com>
committer-time 1191999972
committer-tz -0700
filename lib/grit.rb
summary implement Grit#heads
require 'grit/errors'
d01a4cfad6ea50285c4710243e3cbe019d381eba 9 10 1
author Tom Preston-Werner
author-mail <tom@mojombo.com>
author-time 1192032303
author-tz -0700
committer Tom Preston-Werner
committer-mail <tom@mojombo.com>
committer-time 1192032303
committer-tz -0700
filename lib/grit.rb
summary convert to Grit module, refactor to be more OO
require 'grit/git'
4c8124ffcf4039d292442eeccabdeca5af5c5017 9 11 1
require 'grit/head'
a47fd41f3aa4610ea527dcc1669dfdb9c15c5425 10 12 1
author Tom Preston-Werner
author-mail <tom@mojombo.com>
author-time 1192002639
author-tz -0700
committer Tom Preston-Werner
committer-mail <tom@mojombo.com>
committer-time 1192002639
committer-tz -0700
filename lib/grit.rb
summary add more comments throughout
require 'grit/commit'
b17b974691f0a26f26908495d24d9c4c718920f8 13 13 1
author Tom Preston-Werner
author-mail <tom@mojombo.com>
author-time 1192271832
author-tz -0700
committer Tom Preston-Werner
committer-mail <tom@mojombo.com>
committer-time 1192271832
committer-tz -0700
filename lib/grit.rb
summary started implementing Tree
require 'grit/tree'
74fd66519e983a0f29e16a342a6059dbffe36020 14 14 1
author Tom Preston-Werner
author-mail <tom@mojombo.com>
author-time 1192317005
author-tz -0700
committer Tom Preston-Werner
committer-mail <tom@mojombo.com>
committer-time 1192317005
committer-tz -0700
filename lib/grit.rb
summary add Blob
require 'grit/blob'
d01a4cfad6ea50285c4710243e3cbe019d381eba 12 15 1
require 'grit/repo'
634396b2f541a9f2d58b00be1a07f0c358b999b3 9 16 1
d01a4cfad6ea50285c4710243e3cbe019d381eba 14 17 1
module Grit
b6e1b765e0c15586a2c5b9832854f95defd71e1f 18 18 6
author Tom Preston-Werner
author-mail <tom@mojombo.com>
author-time 1192860483
author-tz -0700
committer Tom Preston-Werner
committer-mail <tom@mojombo.com>
committer-time 1192860483
committer-tz -0700
filename lib/grit.rb
summary implement Repo.init_bare
class << self
b6e1b765e0c15586a2c5b9832854f95defd71e1f 19 19
attr_accessor :debug
b6e1b765e0c15586a2c5b9832854f95defd71e1f 20 20
end
b6e1b765e0c15586a2c5b9832854f95defd71e1f 21 21
b6e1b765e0c15586a2c5b9832854f95defd71e1f 22 22
self.debug = false
b6e1b765e0c15586a2c5b9832854f95defd71e1f 23 23
634396b2f541a9f2d58b00be1a07f0c358b999b3 11 24 2
VERSION = '1.0.0'
634396b2f541a9f2d58b00be1a07f0c358b999b3 12 25
end
Binary file not shown.
@@ -0,0 +1,177 @@
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 1 1 83
author Sebastian Thiel
author-mail <byronimo@gmail.com>
author-time 1420715996
author-tz +0100
committer Sebastian Thiel
committer-mail <byronimo@gmail.com>
committer-time 1420716149
committer-tz +0100
summary Fixed PY3 support.
boundary
filename README.md
## GitPython
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 2 2
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 3 3
GitPython is a python library used to interact with git repositories, high-level like git-porcelain, or low-level like git-plumbing.
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 4 4
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 5 5
It provides abstractions of git objects for easy access of repository data, and additionally allows you to access the git repository more directly using either a pure python implementation, or the faster, but more resource intensive git command implementation.
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 6 6
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 7 7
The object database implementation is optimized for handling large quantities of objects and large datasets, which is achieved by using low-level structures and data streaming.
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 8 8
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 9 9
### REQUIREMENTS
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 10 10
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 11 11
* Git ( tested with 1.8.3.4 )
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 12 12
* Python Nose - used for running the tests
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 13 13
- Tested with nose 1.3.0
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 14 14
* Mock by Michael Foord used for tests
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 15 15
- Tested with 1.0.1
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 16 16
* Coverage - used for tests coverage
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 17 17
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 18 18
The list of dependencies are listed in /requirements.txt and /test-requirements.txt. The installer takes care of installing them for you though.
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 19 19
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 20 20
### INSTALL
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 21 21
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 22 22
[![Latest Version](https://pypip.in/version/GitPython/badge.svg)](https://pypi.python.org/pypi/GitPython/)
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 23 23
[![Supported Python Versions](https://pypip.in/py_versions/GitPython/badge.svg)](https://pypi.python.org/pypi/GitPython/)
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 24 24
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 25 25
If you have downloaded the source code:
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 26 26
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 27 27
python setup.py install
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 28 28
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 29 29
or if you want to obtain a copy from the Pypi repository:
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 30 30
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 31 31
pip install gitpython
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 32 32
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 33 33
Both commands will install the required package dependencies.
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 34 34
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 35 35
A distribution package can be obtained for manual installation at:
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 36 36
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 37 37
http://pypi.python.org/pypi/GitPython
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 38 38
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 39 39
### RUNNING TESTS
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 40 40
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 41 41
The easiest way to run test is by using [tox](https://pypi.python.org/pypi/tox) a wrapper around virtualenv. It will take care of setting up environnements with the proper dependencies installed and execute test commands. To install it simply:
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 42 42
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 43 43
pip install tox
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 44 44
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 45 45
Then run:
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 46 46
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 47 47
tox
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 48 48
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 49 49
### SOURCE
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 50 50
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 51 51
GitPython's git repo is available on GitHub, which can be browsed at [github](https://github.com/gitpython-developers/GitPython) and cloned like that:
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 52 52
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 53 53
git clone git://github.com/gitpython-developers/GitPython.git git-python
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 54 54
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 55 55
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 56 56
### INFRASTRUCTURE
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 57 57
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 58 58
* [User Documentation](http://gitpython.readthedocs.org)
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 59 59
* [Mailing List](http://groups.google.com/group/git-python)
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 60 60
* [Issue Tracker](https://github.com/gitpython-developers/GitPython/issues)
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 61 61
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 62 62
### LICENSE
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 63 63
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 64 64
New BSD License. See the LICENSE file.
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 65 65
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 66 66
### DEVELOPMENT STATUS
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 67 67
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 68 68
[![Build Status](https://travis-ci.org/gitpython-developers/GitPython.svg?branch=0.3)](https://travis-ci.org/gitpython-developers/GitPython)
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 69 69
[![Coverage Status](https://coveralls.io/repos/gitpython-developers/GitPython/badge.png?branch=master)](https://coveralls.io/r/gitpython-developers/GitPython?branch=master)
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 70 70
[![Documentation Status](https://readthedocs.org/projects/gitpython/badge/?version=stable)](https://readthedocs.org/projects/gitpython/?badge=stable)
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 71 71
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 72 72
Now that there seems to be a massive user base, this should be motivation enough to let git-python return to a proper state, which means
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 73 73
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 74 74
* no open pull requests
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 75 75
* no open issues describing bugs
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 76 76
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 77 77
#### FUTURE GOALS
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 78 78
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 79 79
There has been a lot of work in the master branch, which is the direction I want git-python to go. Namely, it should be able to freely mix and match the back-end used, depending on your requirements and environment.
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 80 80
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 81 81
* make new master work similarly to 0.3, but with the option to swap for at least one additional backend
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 82 82
* make a 1.0 release
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 83 83
* add backends as required
@@ -0,0 +1,30 @@
82b8902e033430000481eb355733cd7065342037 2 2 1
author Sebastian Thiel
author-mail <byronimo@gmail.com>
author-time 1270634931
author-tz +0200
committer Sebastian Thiel
committer-mail <byronimo@gmail.com>
committer-time 1270634931
committer-tz +0200
summary Used this release␍␍ for a first beta of the 0.2 branch of development
previous 501bf602abea7d21c3dbb409b435976e92033145 AUTHORS
filename AUTHORS
82b8902e033430000481eb355733cd7065342037 14 14 1
filename AUTHORS
c76852d0bff115720af3f27acdb084c59361e5f6 1 1 1
author Michael Trier
author-mail <mtrier@gmail.com>
author-time 1232829627
author-tz -0500
committer Michael Trier
committer-mail <mtrier@gmail.com>
committer-time 1232829627
committer-tz -0500
summary Lots of spring cleaning and added in Sphinx documentation.
previous bcd57e349c08bd7f076f8d6d2f39b702015358c1 AUTHORS
filename AUTHORS
c76852d0bff115720af3f27acdb084c59361e5f6 2 3 11
filename AUTHORS
c76852d0bff115720af3f27acdb084c59361e5f6 13 15 2
filename AUTHORS
@@ -0,0 +1,33 @@
82b8902e033430000481eb355733cd7065342037 2 2 1
author Sebastian Thiel
author-mail <byronimo@gmail.com>
author-time 1270634931
author-tz +0200
committer Sebastian Thiel
committer-mail <byronimo@gmail.com>
committer-time 1270634931
committer-tz +0200
summary Used this release for a first beta of the 0.2 branch of development
previous 501bf602abea7d21c3dbb409b435976e92033145 AUTHORS
filename AUTHORS
82b8902e033430000481eb355733cd7065342037 14 14 1
previous 501bf602abea7d21c3dbb409b435976e92033145 AUTHORS
filename AUTHORS
c76852d0bff115720af3f27acdb084c59361e5f6 1 1 1
author Michael Trier
author-mail <mtrier@gmail.com>
author-time 1232829627
author-tz -0500
committer Michael Trier
committer-mail <mtrier@gmail.com>
committer-time 1232829627
committer-tz -0500
summary Lots of spring cleaning and added in Sphinx documentation.
previous bcd57e349c08bd7f076f8d6d2f39b702015358c1 AUTHORS
filename AUTHORS
c76852d0bff115720af3f27acdb084c59361e5f6 2 3 11
previous bcd57e349c08bd7f076f8d6d2f39b702015358c1 AUTHORS
filename AUTHORS
c76852d0bff115720af3f27acdb084c59361e5f6 13 15 2
previous bcd57e349c08bd7f076f8d6d2f39b702015358c1 AUTHORS
filename AUTHORS
@@ -0,0 +1,6 @@
import sys
with open(sys.argv[1]) as fd:
for line in fd.readlines():
sys.stdout.write(line)
sys.stderr.write(line)
@@ -0,0 +1 @@
Hello world
@@ -0,0 +1 @@
Hello world
@@ -0,0 +1 @@
11
@@ -0,0 +1,6 @@
tree 9f1a495d7d9692d24f5caedaa89f5c2c32d59368
parent 492ace2ffce0e426ebeb55e364e987bcf024dd3b
author E.Azer KoÃoÃoÃoculu <azer@kodfabrik.com> 1306710073 +0300
committer E.Azer KoÃoÃoÃoculu <azer@kodfabrik.com> 1306710073 +0300
add environjs
@@ -0,0 +1,30 @@
tree cefbccb4843d821183ae195e70a17c9938318945
parent 904435cf76a9bdd5eb41b1c4e049d5a64f3a8400
author Jon Mason <jon.mason@intel.com> 1367013117 -0700
committer Jon Mason <jon.mason@intel.com> 1368640702 -0700
gpgsig -----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)
iQIcBAABAgAGBQJRk8zMAAoJEG5mS6x6i9IjsTEP/0v2Wx/i7dqyKban6XMIhVdj
uI0DycfXqnCCZmejidzeao+P+cuK/ZAA/b9fU4MtwkDm2USvnIOrB00W0isxsrED
sdv6uJNa2ybGjxBolLrfQcWutxGXLZ1FGRhEvkPTLMHHvVriKoNFXcS7ewxP9MBf
NH97K2wauqA+J4BDLDHQJgADCOmLrGTAU+G1eAXHIschDqa6PZMH5nInetYZONDh
3SkOOv8VKFIF7gu8X7HC+7+Y8k8U0TW0cjlQ2icinwCc+KFoG6GwXS7u/VqIo1Yp
Tack6sxIdK7NXJhV5gAeAOMJBGhO0fHl8UUr96vGEKwtxyZhWf8cuIPOWLk06jA0
g9DpLqmy/pvyRfiPci+24YdYRBua/vta+yo/Lp85N7Hu/cpIh+q5WSLvUlv09Dmo
TTTG8Hf6s3lEej7W8z2xcNZoB6GwXd8buSDU8cu0I6mEO9sNtAuUOHp2dBvTA6cX
PuQW8jg3zofnx7CyNcd3KF3nh2z8mBcDLgh0Q84srZJCPRuxRcp9ylggvAG7iaNd
XMNvSK8IZtWLkx7k3A3QYt1cN4y1zdSHLR2S+BVCEJea1mvUE+jK5wiB9S4XNtKm
BX/otlTa8pNE3fWYBxURvfHnMY4i3HQT7Bc1QjImAhMnyo2vJk4ORBJIZ1FTNIhJ
JzJMZDRLQLFvnzqZuCjE
=przd
-----END PGP SIGNATURE-----
NTB: Multiple NTB client fix
Fix issue with adding multiple ntb client devices to the ntb virtual
bus. Previously, multiple devices would be added with the same name,
resulting in crashes. To get around this issue, add a unique number to
the device when it is added.
Signed-off-by: Jon Mason <jon.mason@intel.com>
@@ -0,0 +1,54 @@
diff --git a/lib/grit/commit.rb b/lib/grit/commit.rb
index a093bb1db8e884cccf396b297259181d1caebed4..80fd3d527f269ecbd570b65b8e21fd85baedb6e9 100644
--- a/lib/grit/com mit.rb
+++ b/lib/grit/com mit.rb
@@ -156,12 +156,8 @@ module Grit
def diffs
if parents.empty?
- diff = @repo.git.show({:full_index => true, :pretty => 'raw'}, @id)
- if diff =~ /diff --git a/
- diff = diff.sub(/.+?(diff --git a)/m, '\1')
- else
- diff = ''
- end
+ diff = @repo.git.show({:full_index => true, :pretty => 'raw'}, @id)
+ diff = diff.sub(/.+?(diff --git a)/m, '\1')
Diff.list_from_string(@repo, diff)
else
self.class.diff(@repo, parents.first.id, @id)
diff --git a/test/fixtures/show_empty_commit b/test/fixtures/show_empty_commit
deleted file mode 100644
index ea25e32a409fdf74c1b9268820108d1c16dcc553..0000000000000000000000000000000000000000
--- a/test/fixtures/show_empty_commit
+++ /dev/null
@@ -1,6 +0,0 @@
-commit 1e3824339762bd48316fe87bfafc853732d43264
-tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
-author Tom Preston-Werner <tom@mojombo.com> 1157392833 +0000
-committer Tom Preston-Werner <tom@mojombo.com> 1157392833 +0000
-
- initial directory structure
diff --git a/test/test_commit.rb b/test/test_commit.rb
index fdeb9000089b052f0b31a845e0173e9b089e06a0..bdbc450e08084d7d611e985cfa12fb424cab29b2 100644
--- a/test/test_commit.rb
+++ b/test/test_commit.rb
@@ -98,18 +98,6 @@ class TestCommit < Test::Unit::TestCase
assert_equal true, diffs[5].new_file
end
- def test_diffs_on_initial_import_with_empty_commit
- Git.any_instance.expects(:show).with(
- {:full_index => true, :pretty => 'raw'},
- '634396b2f541a9f2d58b00be1a07f0c358b999b3'
- ).returns(fixture('show_empty_commit'))
-
- @c = Commit.create(@r, :id => '634396b2f541a9f2d58b00be1a07f0c358b999b3')
- diffs = @c.diffs
-
- assert_equal [], diffs
- end
-
# to_s
def test_to_s
@@ -0,0 +1,19 @@
diff --git a/lib/grit/commit.rb b/lib/grit/commit.rb
index a093bb1db8e884cccf396b297259181d1caebed4..80fd3d527f269ecbd570b65b8e21fd85baedb6e9 100644
--- a/lib/grit/commit.rb
+++ b/lib/grit/commit.rb
@@ -156,12 +156,8 @@ module Grit
def diffs
if parents.empty?
- diff = @repo.git.show({:full_index => true, :pretty => 'raw'}, @id)
- if diff =~ /diff --git a/
- diff = diff.sub(/.+?(diff --git a)/m, '\1')
- else
- diff = ''
- end
+ diff = @repo.git.show({:full_index => true, :pretty => 'raw'}, @id)
+ diff = diff.sub(/.+?(diff --git a)/m, '\1')
Diff.list_from_string(@repo, diff)
else
self.class.diff(@repo, parents.first.id, @id)
@@ -0,0 +1 @@
:100644 100644 739bc65220ad90e9ebfa2d6af1723b97555569a4 0000000000000000000000000000000000000000 M README.md
@@ -0,0 +1,10 @@
diff --git a/this b/this
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/this b/this
new file mode 120000
index 0000000000000000000000000000000000000000..42061c01a1c70097d1e4579f29a5adf40abdec95
--- /dev/null
+++ b/this
@@ -0,0 +1 @@
+that
@@ -0,0 +1 @@
:100644 120000 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 42061c01a1c70097d1e4579f29a5adf40abdec95 T this
@@ -0,0 +1,15 @@
diff --git a/lib/grit/diff.rb b/lib/grit/diff.rb
index 537955bb86a8ceaa19aea89e75ccbea5ce6f2698..00b0b4a67eca9242db5f8991e99625acd55f040c 100644
--- a/lib/grit/diff.rb
+++ b/lib/grit/diff.rb
@@ -27,6 +27,10 @@ module Grit
while !lines.empty?
m, a_path, b_path = *lines.shift.match(%r{^diff --git a/(\S+) b/(\S+)$})
+ if lines.first =~ /^old mode/
+ 2.times { lines.shift }
+ end
+
new_file = false
deleted_file = false
@@ -0,0 +1,7 @@
diff --git a/file with spaces b/file with spaces
new file mode 100644
index 0000000000000000000000000000000000000000..75c620d7b0d3b0100415421a97f553c979d75174
--- /dev/null
+++ b/file with spaces
@@ -0,0 +1 @@
+ohai
@@ -0,0 +1,201 @@
commit 634396b2f541a9f2d58b00be1a07f0c358b999b3
Author: Tom Preston-Werner <tom@mojombo.com>
Date: Tue Oct 9 23:18:20 2007 -0700
initial grit setup
diff --git a/History.txt b/History.txt
new file mode 100644
index 0000000000000000000000000000000000000000..81d2c27608b352814cbe979a6acd678d30219678
--- /dev/null
+++ b/History.txt
@@ -0,0 +1,5 @@
+== 1.0.0 / 2007-10-09
+
+* 1 major enhancement
+ * Birthday!
+
diff --git a/Manifest.txt b/Manifest.txt
new file mode 100644
index 0000000000000000000000000000000000000000..641972d82c6d1b51122274ae8f6a0ecdfb56ee22
--- /dev/null
+++ b/Manifest.txt
@@ -0,0 +1,7 @@
+History.txt
+Manifest.txt
+README.txt
+Rakefile
+bin/grit
+lib/grit.rb
+test/test_grit.rb
\ No newline at end of file
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8b1e02c0fb554eed2ce2ef737a68bb369d7527df
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,48 @@
+grit
+ by FIX (your name)
+ FIX (url)
+
+== DESCRIPTION:
+
+FIX (describe your package)
+
+== FEATURES/PROBLEMS:
+
+* FIX (list of features or problems)
+
+== SYNOPSIS:
+
+ FIX (code sample of usage)
+
+== REQUIREMENTS:
+
+* FIX (list of requirements)
+
+== INSTALL:
+
+* FIX (sudo gem install, anything else)
+
+== LICENSE:
+
+(The MIT License)
+
+Copyright (c) 2007 FIX
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000000000000000000000000000000000000..ff69c3684a18592c741332b290492aa39d980e02
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,17 @@
+# -*- ruby -*-
+
+require 'rubygems'
+require 'hoe'
+require './lib/grit.rb'
+
+Hoe.new('grit', GitPython.VERSION) do |p|
+ p.rubyforge_name = 'grit'
+ # p.author = 'FIX'
+ # p.email = 'FIX'
+ # p.summary = 'FIX'
+ # p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
+ # p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
+end
+
+# vim: syntax=Ruby
diff --git a/bin/grit b/bin/grit
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/grit.rb b/lib/grit.rb
new file mode 100644
index 0000000000000000000000000000000000000000..32cec87d1e78946a827ddf6a8776be4d81dcf1d1
--- /dev/null
+++ b/lib/grit.rb
@@ -0,0 +1,12 @@
+$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
+
+# core
+
+# stdlib
+
+# internal requires
+require 'grit/grit'
+
+class Grit
+ VERSION = '1.0.0'
+end
\ No newline at end of file
diff --git a/lib/grit/errors.rb b/lib/grit/errors.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b3be31553741937607a89be8b6a2ab1df208852e
--- /dev/null
+++ b/lib/grit/errors.rb
@@ -0,0 +1,4 @@
+class Grit
+ class InvalidGitRepositoryError < StandardError
+ end
+end
\ No newline at end of file
diff --git a/lib/grit/grit.rb b/lib/grit/grit.rb
new file mode 100644
index 0000000000000000000000000000000000000000..48fd36e16081ec09903f7a0e2253b3d16f9efb01
--- /dev/null
+++ b/lib/grit/grit.rb
@@ -0,0 +1,24 @@
+class Grit
+ attr_accessor :path
+
+ # Create a new Grit instance
+ # +path+ is the path to either the root git directory or the bare git repo
+ #
+ # Examples
+ # g = Grit.new("/Users/tom/dev/grit")
+ # g = Grit.new("/Users/tom/public/grit.git")
+ def initialize(path)
+ if File.exist?(File.join(path, '.git'))
+ self.path = File.join(path, '.git')
+ elsif File.exist?(path) && path =~ /\.git$/
+ self.path = path
+ else
+ raise InvalidGitRepositoryError.new(path) unless File.exist?(path)
+ end
+ end
+
+ # Return the project's description. Taken verbatim from REPO/description
+ def description
+ File.open(File.join(self.path, 'description')).read.chomp
+ end
+end
\ No newline at end of file
diff --git a/test/helper.rb b/test/helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..56e21da6b4ce3021d2754775dfa589947a4e37e5
--- /dev/null
+++ b/test/helper.rb
@@ -0,0 +1,5 @@
+require File.join(File.dirname(__FILE__), *%w[.. lib grit])
+
+require 'test/unit'
+
+GRIT_REPO = File.join(File.dirname(__FILE__), *%w[..])
diff --git a/test/test_grit.rb b/test/test_grit.rb
new file mode 100644
index 0000000000000000000000000000000000000000..93aa481b37629797df739380306ae689e13f2855
--- /dev/null
+++ b/test/test_grit.rb
@@ -0,0 +1,11 @@
+require File.dirname(__FILE__) + '/helper'
+
+class TestGrit < Test::Unit::TestCase
+ def setup
+ @g = Grit.new(GRIT_REPO)
+ end
+
+ def test_description
+ assert_equal "Grit is a ruby library for interfacing with git repositories.", @g.description
+ end
+end
\ No newline at end of file
@@ -0,0 +1,100 @@
diff --git a/etc/sublime-text/git-python.sublime-project b/etc/sublime-text/git-python.sublime-project
index 3dab9f6562ecb0408d9ece8dd63cc4461d280113..9c99a2cff7dc918fbbb61cd57d5d98750a1ef6c5 100644
--- a/etc/sublime-text/git-python.sublime-project
+++ b/etc/sublime-text/git-python.sublime-project
@@ -23,7 +23,7 @@
]
},
// GITDB
- ////////
+ // ////////
{
"follow_symlinks": true,
"path": "../../git/ext/gitdb",
@@ -42,8 +42,8 @@
".tox",
]
},
- // // SMMAP
- // ////////
+ // // // SMMAP
+ // // ////////
{
"follow_symlinks": true,
"path": "../../git/ext/gitdb/gitdb/ext/smmap",
diff --git a/git/diff.py b/git/diff.py
index 24e47bad9d79534d3cf474fec4f79e6fef122bb1..c1ad532e0217e293906bcfef43c523d6a8e21568 100644
--- a/git/diff.py
+++ b/git/diff.py
@@ -302,13 +302,21 @@ class Diff(object):
diff_header = cls.re_header.match
for diff in ('\n' + text).split('\ndiff --git')[1:]:
header = diff_header(diff)
- assert header is not None, "Failed to parse diff header from " % diff
+ assert header is not None, "Failed to parse diff header from '%s'" % diff
a_path, b_path, similarity_index, rename_from, rename_to, \
old_mode, new_mode, new_file_mode, deleted_file_mode, \
a_blob_id, b_blob_id, b_mode = header.groups()
new_file, deleted_file = bool(new_file_mode), bool(deleted_file_mode)
+ # if a_path.startswith('a/'):
+ # a_path = a_path[2:]
+ # if b_path.startswith('b/'):
+ # b_path = b_path[2:]
+
+ for item in (a_path, b_path, a_blob_id, b_blob_id, old_mode, deleted_file_mode, new_mode, new_file_mode, b_mode, new_file, deleted_file, rename_from, rename_to, diff[header.end():]):
+ print( "####")
+ print(item)
index.append(Diff(repo, a_path, b_path, a_blob_id, b_blob_id,
old_mode or deleted_file_mode, new_mode or new_file_mode or b_mode,
new_file, deleted_file, rename_from, rename_to, diff[header.end():]))
diff --git a/git/ext/gitdb b/git/ext/gitdb
index f2233fbf40f3f69309ce5cc714e99fcbdcd33ec3..a88a777df3909a61be97f1a7b1194dad6de25702 160000
--- a/git/ext/gitdb
+++ b/git/ext/gitdb
@@ -1 +1 @@
-Subproject commit f2233fbf40f3f69309ce5cc714e99fcbdcd33ec3
+Subproject commit a88a777df3909a61be97f1a7b1194dad6de25702-dirty
diff --git a/git/test/fixtures/diff_patch_binary b/git/test/fixtures/diff_patch_binary
new file mode 100644
index 0000000000000000000000000000000000000000..c92ccd6ebc92a871d38ad7cb8a48bcdb1a5dbc33
--- /dev/null
+++ b/git/test/fixtures/diff_patch_binary
@@ -0,0 +1,3 @@
+diff --git a/rps b/rps
+index f4567df37451b230b1381b1bc9c2bcad76e08a3c..736bd596a36924d30b480942e9475ce0d734fa0d 100755
+Binary files a/rps and b/rps differ
diff --git a/git/test/fixtures/diff_raw_binary b/git/test/fixtures/diff_raw_binary
new file mode 100644
index 0000000000000000000000000000000000000000..d4673fa41ee8413384167fc7b9f25e4daf18a53a
--- /dev/null
+++ b/git/test/fixtures/diff_raw_binary
@@ -0,0 +1 @@
+:100755 100755 f4567df37451b230b1381b1bc9c2bcad76e08a3c 736bd596a36924d30b480942e9475ce0d734fa0d M rps
diff --git a/git/test/test_diff.py b/git/test/test_diff.py
index ce0f64f2261bd8de063233108caac1f26742c1fd..4de26f8884fd048ac7f10007f2bf7c7fa3fa60f4 100644
--- a/git/test/test_diff.py
+++ b/git/test/test_diff.py
@@ -65,6 +65,21 @@ class TestDiff(TestBase):
assert diff.rename_to == 'that'
assert len(list(diffs.iter_change_type('R'))) == 1
+ def test_binary_diff(self):
+ for method, file_name in ((Diff._index_from_patch_format, 'diff_patch_binary'),
+ (Diff._index_from_raw_format, 'diff_raw_binary')):
+ res = method(None, StringProcessAdapter(fixture(file_name)).stdout)
+ assert len(res) == 1
+ assert len(list(res.iter_change_type('M'))) == 1
+ if res[0].diff:
+ assert res[0].diff == "Binary files a/rps and b/rps differ\n", "in patch mode, we get a diff text"
+ assert isinstance(str(res[0]), str), "This call should just work"
+ # end for each method to test
+
+ def test_diff_index(self):
+ res = self.rorepo.index.diff('17f5d13a7a741dcbb2a30e147bdafe929cff4697', create_patch=True)
+ assert len(res) == 3
+
def test_diff_patch_format(self):
# test all of the 'old' format diffs for completness - it should at least
# be able to deal with it
@@ -0,0 +1 @@
:100644 000000 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0000000000000000000000000000000000000000 D
@@ -0,0 +1,8 @@
@@ -0,0 +1,7 @@
+=======
+CHANGES
+=======
+
+0.1.0
+=====
+initial release
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
diff --git a/conf/global_settings.py b/conf/global_settings.py
old mode 100644
new mode 100755
index 9ec1bac000000000000000000000000000000000..1c4f83b000000000000000000000000000000000
--- a/conf/global_settings.py
+++ b/conf/global_settings.py
@@ -58,6 +58,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
)
MIDDLEWARE_CLASSES = (
+ "django.middleware.cache.CacheMiddleware",
"django.middleware.common.CommonMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
@@ -0,0 +1,2 @@
29 18 a.txt
0 5 b.txt
@@ -0,0 +1,610 @@
diff --git a/.gitignore b/.gitignore
index 4ebc8aea50e0a67e000ba29a30809d0a7b9b2666..2dd02534615434d88c51307beb0f0092f21fd103 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
coverage
+pkg
diff --git a/Manifest.txt b/Manifest.txt
index 641972d82c6d1b51122274ae8f6a0ecdfb56ee22..38bf80c54a526e76d74820a0f48606fe1ca7b1be 100644
--- a/Manifest.txt
+++ b/Manifest.txt
@@ -4,4 +4,31 @@ README.txt
Rakefile
bin/grit
lib/grit.rb
-test/test_grit.rb
\ No newline at end of file
+lib/grit/actor.rb
+lib/grit/blob.rb
+lib/grit/commit.rb
+lib/grit/errors.rb
+lib/grit/git.rb
+lib/grit/head.rb
+lib/grit/lazy.rb
+lib/grit/repo.rb
+lib/grit/tree.rb
+test/fixtures/blame
+test/fixtures/cat_file_blob
+test/fixtures/cat_file_blob_size
+test/fixtures/for_each_ref
+test/fixtures/ls_tree_a
+test/fixtures/ls_tree_b
+test/fixtures/rev_list
+test/fixtures/rev_list_single
+test/helper.rb
+test/profile.rb
+test/suite.rb
+test/test_actor.rb
+test/test_blob.rb
+test/test_commit.rb
+test/test_git.rb
+test/test_head.rb
+test/test_reality.rb
+test/test_repo.rb
+test/test_tree.rb
diff --git a/README.txt b/README.txt
index 8b1e02c0fb554eed2ce2ef737a68bb369d7527df..fca94f84afd7d749c62626011f972a509f6a5ac6 100644
--- a/README.txt
+++ b/README.txt
@@ -1,32 +1,185 @@
grit
- by FIX (your name)
- FIX (url)
+ by Tom Preston-Werner
+ grit.rubyforge.org
== DESCRIPTION:
+
+Grit is a Ruby library for extracting information from a git repository in and
+object oriented manner.
+
+== REQUIREMENTS:
+
+* git (http://git.or.cz) tested with 1.5.3.4
+
+== INSTALL:
+
+sudo gem install grit
+
+== USAGE:
+
+Grit gives you object model access to your git repository. Once you have
+created a repository object, you can traverse it to find parent commit(s),
+trees, blobs, etc.
+
+= Initialize a Repo object
+
+The first step is to create a GitPython.Repo object to represent your repo. I
+include the Grit module so reduce typing.
+
+ include Grit
+ repo = Repo.new("/Users/tom/dev/grit")
-FIX (describe your package)
+In the above example, the directory /Users/tom/dev/grit is my working
+repo and contains the .git directory. You can also initialize Grit with a
+bare repo.
-== FEATURES/PROBLEMS:
+ repo = Repo.new("/var/git/grit.git")
-* FIX (list of features or problems)
+= Getting a list of commits
-== SYNOPSIS:
+From the Repo object, you can get a list of commits as an array of Commit
+objects.
- FIX (code sample of usage)
+ repo.commits
+ # => [#<GitPython.Commit "e80bbd2ce67651aa18e57fb0b43618ad4baf7750">,
+ #<GitPython.Commit "91169e1f5fa4de2eaea3f176461f5dc784796769">,
+ #<GitPython.Commit "038af8c329ef7c1bae4568b98bd5c58510465493">,
+ #<GitPython.Commit "40d3057d09a7a4d61059bca9dca5ae698de58cbe">,
+ #<GitPython.Commit "4ea50f4754937bf19461af58ce3b3d24c77311d9">]
+
+Called without arguments, Repo#commits returns a list of up to ten commits
+reachable by the master branch (starting at the latest commit). You can ask
+for commits beginning at a different branch, commit, tag, etc.
-== REQUIREMENTS:
+ repo.commits('mybranch')
+ repo.commits('40d3057d09a7a4d61059bca9dca5ae698de58cbe')
+ repo.commits('v0.1')
+
+You can specify the maximum number of commits to return.
-* FIX (list of requirements)
+ repo.commits('master', 100)
+
+If you need paging, you can specify a number of commits to skip.
-== INSTALL:
+ repo.commits('master', 10, 20)
+
+The above will return commits 21-30 from the commit list.
+
+= The Commit object
+
+Commit objects contain information about that commit.
+
+ head = repo.commits.first
+
+ head.id
+ # => "e80bbd2ce67651aa18e57fb0b43618ad4baf7750"
+
+ head.parents
+ # => [#<GitPython.Commit "91169e1f5fa4de2eaea3f176461f5dc784796769">]
+
+ head.tree
+ # => #<GitPython.Tree "3536eb9abac69c3e4db583ad38f3d30f8db4771f">
+
+ head.author
+ # => #<GitPython.Actor "Tom Preston-Werner <tom@mojombo.com>">
+
+ head.authored_date
+ # => Wed Oct 24 22:02:31 -0700 2007
+
+ head.committer
+ # => #<GitPython.Actor "Tom Preston-Werner <tom@mojombo.com>">
+
+ head.committed_date
+ # => Wed Oct 24 22:02:31 -0700 2007
+
+ head.message
+ # => "add Actor inspect"
+
+You can traverse a commit's ancestry by chaining calls to #parents.
+
+ repo.commits.first.parents[0].parents[0].parents[0]
+
+The above corresponds to master^^^ or master~3 in git parlance.
+
+= The Tree object
+
+A tree records pointers to the contents of a directory. Let's say you want
+the root tree of the latest commit on the master branch.
+
+ tree = repo.commits.first.tree
+ # => #<GitPython.Tree "3536eb9abac69c3e4db583ad38f3d30f8db4771f">
+
+ tree.id
+ # => "3536eb9abac69c3e4db583ad38f3d30f8db4771f"
+
+Once you have a tree, you can get the contents.
+
+ contents = tree.contents
+ # => [#<GitPython.Blob "4ebc8aea50e0a67e000ba29a30809d0a7b9b2666">,
+ #<GitPython.Blob "81d2c27608b352814cbe979a6acd678d30219678">,
+ #<GitPython.Tree "c3d07b0083f01a6e1ac969a0f32b8d06f20c62e5">,
+ #<GitPython.Tree "4d00fe177a8407dbbc64a24dbfc564762c0922d8">]
+
+This tree contains two Blob objects and two Tree objects. The trees are
+subdirectories and the blobs are files. Trees below the root have additional
+attributes.
+
+ contents.last.name
+ # => "lib"
+
+ contents.last.mode
+ # => "040000"
+
+There is a convenience method that allows you to get a named sub-object
+from a tree.
+
+ tree/"lib"
+ # => #<GitPython.Tree "e74893a3d8a25cbb1367cf241cc741bfd503c4b2">
+
+You can also get a tree directly from the repo if you know its name.
+
+ repo.tree
+ # => #<GitPython.Tree "master">
+
+ repo.tree("91169e1f5fa4de2eaea3f176461f5dc784796769")
+ # => #<GitPython.Tree "91169e1f5fa4de2eaea3f176461f5dc784796769">
+
+= The Blob object
+
+A blob represents a file. Trees often contain blobs.
+
+ blob = tree.contents.first
+ # => #<GitPython.Blob "4ebc8aea50e0a67e000ba29a30809d0a7b9b2666">
+
+A blob has certain attributes.
+
+ blob.id
+ # => "4ebc8aea50e0a67e000ba29a30809d0a7b9b2666"
+
+ blob.name
+ # => "README.txt"
+
+ blob.mode
+ # => "100644"
+
+ blob.size
+ # => 7726
+
+You can get the data of a blob as a string.
+
+ blob.data
+ # => "Grit is a library to ..."
+
+You can also get a blob directly from the repo if you know its name.
-* FIX (sudo gem install, anything else)
+ repo.blob("4ebc8aea50e0a67e000ba29a30809d0a7b9b2666")
+ # => #<GitPython.Blob "4ebc8aea50e0a67e000ba29a30809d0a7b9b2666">
== LICENSE:
(The MIT License)
-Copyright (c) 2007 FIX
+Copyright (c) 2007 Tom Preston-Werner
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/Rakefile b/Rakefile
index 5bfb62163af455ca54422fd0b2e723ba1021ad12..72fde8c9ca87a1c992ce992bab13c3c4f13cddb9 100644
--- a/Rakefile
+++ b/Rakefile
@@ -4,11 +4,11 @@ require './lib/grit.rb'
Hoe.new('grit', GitPython.VERSION) do |p|
p.rubyforge_name = 'grit'
- # p.author = 'FIX'
- # p.email = 'FIX'
- # p.summary = 'FIX'
- # p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
- # p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
+ p.author = 'Tom Preston-Werner'
+ p.email = 'tom@rubyisawesome.com'
+ p.summary = 'Object model interface to a git repo'
+ p.description = p.paragraphs_of('README.txt', 2..2).join("\n\n")
+ p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[2..-1].map { |u| u.strip }
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
end
diff --git a/lib/grit.rb b/lib/grit.rb
index ae0792ae39d4891ebc1af996102a4f9df703394d..ae55fd7961ac49233f6ca515622a61e90d516044 100644
--- a/lib/grit.rb
+++ b/lib/grit.rb
@@ -1,4 +1,4 @@
-$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
+$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
# core
@@ -12,6 +12,8 @@ require 'grit/head'
require 'grit/commit'
require 'grit/tree'
require 'grit/blob'
+require 'grit/actor'
+require 'grit/diff'
require 'grit/repo'
module Grit
@@ -21,5 +23,5 @@ module Grit
self.debug = false
- VERSION = '1.0.0'
+ VERSION = '0.1.0'
end
\ No newline at end of file
diff --git a/lib/grit/actor.rb b/lib/grit/actor.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f733bce6b57c0e5e353206e692b0e3105c2527f4
--- /dev/null
+++ b/lib/grit/actor.rb
@@ -0,0 +1,35 @@
+module Grit
+
+ class Actor
+ attr_reader :name
+ attr_reader :email
+
+ def initialize(name, email)
+ @name = name
+ @email = email
+ end
+
+ # Create an Actor from a string.
+ # +str+ is the string, which is expected to be in regular git format
+ #
+ # Format
+ # John Doe <jdoe@example.com>
+ #
+ # Returns Actor
+ def self.from_string(str)
+ case str
+ when /<.+>/
+ m, name, email = *str.match(/(.*) <(.+?)>/)
+ return self.new(name, email)
+ else
+ return self.new(str, nil)
+ end
+ end
+
+ # Pretty object inspection
+ def inspect
+ %Q{#<GitPython.Actor "#{@name} <#{@email}>">}
+ end
+ end # Actor
+
+end # Grit
\ No newline at end of file
diff --git a/lib/grit/blob.rb b/lib/grit/blob.rb
index c863646d4278bfee2a7bcb64caace6b31f89ef03..87d43fab37844afdc2f8814dba3abdaa791f1370 100644
--- a/lib/grit/blob.rb
+++ b/lib/grit/blob.rb
@@ -81,9 +81,9 @@ module Grit
c = commits[info[:id]]
unless c
c = Commit.create(repo, :id => info[:id],
- :author => info[:author],
+ :author => Actor.from_string(info[:author] + ' ' + info[:author_email]),
:authored_date => info[:author_date],
- :committer => info[:committer],
+ :committer => Actor.from_string(info[:committer] + ' ' + info[:committer_email]),
:committed_date => info[:committer_date],
:message => info[:summary])
commits[info[:id]] = c
@@ -102,11 +102,6 @@ module Grit
def inspect
%Q{#<GitPython.Blob "#{@id}">}
end
-
- # private
-
- def self.read_
- end
end # Blob
end # Grit
\ No newline at end of file
diff --git a/lib/grit/commit.rb b/lib/grit/commit.rb
index c2a9e2f81657b19925fe9bab4bc5d7ac130e5880..cd9c3e3184c97e83a8982fab9499cad3aec339f6 100644
--- a/lib/grit/commit.rb
+++ b/lib/grit/commit.rb
@@ -136,6 +136,11 @@ module Grit
commits
end
+ def self.diff(repo, id)
+ text = repo.git.diff({:full_index => true}, id)
+ Diff.list_from_string(repo, text)
+ end
+
# Convert this Commit to a String which is just the SHA1 id
def to_s
@id
@@ -153,7 +158,7 @@ module Grit
# Returns [String (actor name and email), Time (acted at time)]
def self.actor(line)
m, actor, epoch = *line.match(/^.+? (.*) (\d+) .*$/)
- [actor, Time.at(epoch.to_i)]
+ [Actor.from_string(actor), Time.at(epoch.to_i)]
end
end # Commit
diff --git a/lib/grit/git.rb b/lib/grit/git.rb
index 1d5251d40fb65ac89184ec662a3e1b04d0c24861..98eeddda5ed2b0e215e21128112393bdc9bc9039 100644
--- a/lib/grit/git.rb
+++ b/lib/grit/git.rb
@@ -13,17 +13,6 @@ module Grit
self.git_dir = git_dir
end
- # Converstion hash from Ruby style options to git command line
- # style options
- TRANSFORM = {:max_count => "--max-count=",
- :skip => "--skip=",
- :pretty => "--pretty=",
- :sort => "--sort=",
- :format => "--format=",
- :since => "--since=",
- :p => "-p",
- :s => "-s"}
-
# Run the given git command with the specified arguments and return
# the result as a String
# +cmd+ is the command
@@ -52,12 +41,19 @@ module Grit
def transform_options(options)
args = []
options.keys.each do |opt|
- if TRANSFORM[opt]
+ if opt.to_s.size == 1
+ if options[opt] == true
+ args << "-#{opt}"
+ else
+ val = options.delete(opt)
+ args << "-#{opt.to_s} #{val}"
+ end
+ else
if options[opt] == true
- args << TRANSFORM[opt]
+ args << "--#{opt.to_s.gsub(/_/, '-')}"
else
val = options.delete(opt)
- args << TRANSFORM[opt] + val.to_s
+ args << "--#{opt.to_s.gsub(/_/, '-')}=#{val}"
end
end
end
diff --git a/lib/grit/repo.rb b/lib/grit/repo.rb
index 624991d07e240ae66ff2a0dc55e2f2b5e262c75b..63bf03b839374c96a3d42a07d56681a797f52a71 100644
--- a/lib/grit/repo.rb
+++ b/lib/grit/repo.rb
@@ -93,6 +93,17 @@ module Grit
def blob(id)
Blob.create(self, :id => id)
end
+
+ # The commit log for a treeish
+ #
+ # Returns GitPython.Commit[]
+ def log(commit = 'master', path = nil, options = {})
+ default_options = {:pretty => "raw"}
+ actual_options = default_options.merge(options)
+ arg = path ? "#{commit} -- #{path}" : commit
+ commits = self.git.log(actual_options, arg)
+ Commit.list_from_string(self, commits)
+ end
# The diff from commit +a+ to commit +b+, optionally restricted to the given file(s)
# +a+ is the base commit
@@ -121,4 +132,4 @@ module Grit
end
end # Repo
-end # Grit
\ No newline at end of file
+end # Grit
diff --git a/test/test_actor.rb b/test/test_actor.rb
new file mode 100644
index 0000000000000000000000000000000000000000..08391f12336831d048122c8d13bc8404f27e6b91
--- /dev/null
+++ b/test/test_actor.rb
@@ -0,0 +1,28 @@
+require File.dirname(__FILE__) + '/helper'
+
+class TestActor < Test::Unit::TestCase
+ def setup
+
+ end
+
+ # from_string
+
+ def test_from_string_should_separate_name_and_email
+ a = Actor.from_string("Tom Werner <tom@example.com>")
+ assert_equal "Tom Werner", a.name
+ assert_equal "tom@example.com", a.email
+ end
+
+ def test_from_string_should_handle_just_name
+ a = Actor.from_string("Tom Werner")
+ assert_equal "Tom Werner", a.name
+ assert_equal nil, a.email
+ end
+
+ # inspect
+
+ def test_inspect
+ a = Actor.from_string("Tom Werner <tom@example.com>")
+ assert_equal %Q{#<GitPython.Actor "Tom Werner <tom@example.com>">}, a.inspect
+ end
+end
\ No newline at end of file
diff --git a/test/test_blob.rb b/test/test_blob.rb
index 6fa087d785661843034d03c7e0b917a8a80d5d8c..9ef84cc14266141b070771706b8aeebc3dfbef82 100644
--- a/test/test_blob.rb
+++ b/test/test_blob.rb
@@ -40,9 +40,11 @@ class TestBlob < Test::Unit::TestCase
c = b.first.first
c.expects(:__bake__).times(0)
assert_equal '634396b2f541a9f2d58b00be1a07f0c358b999b3', c.id
- assert_equal 'Tom Preston-Werner', c.author
+ assert_equal 'Tom Preston-Werner', c.author.name
+ assert_equal 'tom@mojombo.com', c.author.email
assert_equal Time.at(1191997100), c.authored_date
- assert_equal 'Tom Preston-Werner', c.committer
+ assert_equal 'Tom Preston-Werner', c.committer.name
+ assert_equal 'tom@mojombo.com', c.committer.email
assert_equal Time.at(1191997100), c.committed_date
assert_equal 'initial grit setup', c.message
# c.expects(:__bake__).times(1)
diff --git a/test/test_commit.rb b/test/test_commit.rb
index 3bd6af75deda05725900eb7fd06e8107df14c655..0936c90e5b29ede2b5214d6dc26d256a8c6646f4 100644
--- a/test/test_commit.rb
+++ b/test/test_commit.rb
@@ -10,9 +10,28 @@ class TestCommit < Test::Unit::TestCase
def test_bake
Git.any_instance.expects(:rev_list).returns(fixture('rev_list_single'))
@c = Commit.create(@r, :id => '4c8124ffcf4039d292442eeccabdeca5af5c5017')
- @c.author # cause bake-age
+ @c.author # bake
- assert_equal "Tom Preston-Werner <tom@mojombo.com>", @c.author
+ assert_equal "Tom Preston-Werner", @c.author.name
+ assert_equal "tom@mojombo.com", @c.author.email
+ end
+
+ # diff
+
+ def test_diff
+ Git.any_instance.expects(:diff).returns(fixture('diff_p'))
+ diffs = Commit.diff(@r, 'master')
+
+ assert_equal 15, diffs.size
+
+ assert_equal '.gitignore', diffs.first.a_path
+ assert_equal '.gitignore', diffs.first.b_path
+ assert_equal '4ebc8ae', diffs.first.a_commit
+ assert_equal '2dd0253', diffs.first.b_commit
+ assert_equal '100644', diffs.first.mode
+ assert_equal false, diffs.first.new_file
+ assert_equal false, diffs.first.deleted_file
+ assert_equal "--- a/.gitignore\n+++ b/.gitignore\n@@ -1 +1,2 @@\n coverage\n+pkg", diffs.first.diff
end
# to_s
diff --git a/test/test_git.rb b/test/test_git.rb
index e615a035d096b6cbc984e2f4213c06d0ac785321..72a18ec424f078f6daee75dbc62265c02ba7a892 100644
--- a/test/test_git.rb
+++ b/test/test_git.rb
@@ -10,6 +10,12 @@ class TestGit < Test::Unit::TestCase
end
def test_transform_options
+ assert_equal ["-s"], @git.transform_options({:s => true})
+ assert_equal ["-s 5"], @git.transform_options({:s => 5})
+
+ assert_equal ["--max-count"], @git.transform_options({:max_count => true})
assert_equal ["--max-count=5"], @git.transform_options({:max_count => 5})
+
+ assert_equal ["-t", "-s"], @git.transform_options({:s => true, :t => true})
end
end
\ No newline at end of file
diff --git a/test/test_repo.rb b/test/test_repo.rb
index d53476a51e3286be270c7b515ec1d65e5c1716e0..114a4464fa248550be10cc4abe0735d6025b5fca 100644
--- a/test/test_repo.rb
+++ b/test/test_repo.rb
@@ -59,9 +59,11 @@ class TestRepo < Test::Unit::TestCase
assert_equal '4c8124ffcf4039d292442eeccabdeca5af5c5017', c.id
assert_equal ["634396b2f541a9f2d58b00be1a07f0c358b999b3"], c.parents.map { |p| p.id }
assert_equal "672eca9b7f9e09c22dcb128c283e8c3c8d7697a4", c.tree.id
- assert_equal "Tom Preston-Werner <tom@mojombo.com>", c.author
+ assert_equal "Tom Preston-Werner", c.author.name
+ assert_equal "tom@mojombo.com", c.author.email
assert_equal Time.at(1191999972), c.authored_date
- assert_equal "Tom Preston-Werner <tom@mojombo.com>", c.committer
+ assert_equal "Tom Preston-Werner", c.committer.name
+ assert_equal "tom@mojombo.com", c.committer.email
assert_equal Time.at(1191999972), c.committed_date
assert_equal "implement Grit#heads", c.message
@@ -125,4 +127,18 @@ class TestRepo < Test::Unit::TestCase
def test_inspect
assert_equal %Q{#<GitPython.Repo "#{File.expand_path(GRIT_REPO)}/.git">}, @r.inspect
end
-end
\ No newline at end of file
+
+ # log
+
+ def test_log
+ Git.any_instance.expects(:log).times(2).with({:pretty => 'raw'}, 'master').returns(fixture('rev_list'))
+
+ assert_equal '4c8124ffcf4039d292442eeccabdeca5af5c5017', @r.log.first.id
+ assert_equal 'ab25fd8483882c3bda8a458ad2965d2248654335', @r.log.last.id
+ end
+
+ def test_log_with_path_and_options
+ Git.any_instance.expects(:log).with({:pretty => 'raw', :max_count => 1}, 'master -- file.rb').returns(fixture('rev_list'))
+ @r.log('master', 'file.rb', :max_count => 1)
+ end
+end
@@ -0,0 +1,3 @@
diff --git a/rps b/rps
index f4567df37451b230b1381b1bc9c2bcad76e08a3c..736bd596a36924d30b480942e9475ce0d734fa0d 100755
Binary files a/rps and b/rps differ
@@ -0,0 +1,89 @@
diff --git a/path/ starting with a space b/path/ starting with a space
new file mode 100644
index 0000000000000000000000000000000000000000..eaf5f7510320b6a327fb308379de2f94d8859a54
--- /dev/null
+++ b/path/ starting with a space
@@ -0,0 +1 @@
+dummy content
diff --git "a/path/\"with-quotes\"" "b/path/\"with-quotes\""
new file mode 100644
index 0000000000000000000000000000000000000000..eaf5f7510320b6a327fb308379de2f94d8859a54
--- /dev/null
+++ "b/path/\"with-quotes\""
@@ -0,0 +1 @@
+dummy content
diff --git a/path/'with-single-quotes' b/path/'with-single-quotes'
new file mode 100644
index 0000000000000000000000000000000000000000..eaf5f7510320b6a327fb308379de2f94d8859a54
--- /dev/null
+++ b/path/'with-single-quotes'
@@ -0,0 +1 @@
+dummy content
diff --git a/path/ending in a space b/path/ending in a space
new file mode 100644
index 0000000000000000000000000000000000000000..eaf5f7510320b6a327fb308379de2f94d8859a54
--- /dev/null
+++ b/path/ending in a space
@@ -0,0 +1 @@
+dummy content
diff --git "a/path/with\ttab" "b/path/with\ttab"
new file mode 100644
index 0000000000000000000000000000000000000000..eaf5f7510320b6a327fb308379de2f94d8859a54
--- /dev/null
+++ "b/path/with\ttab"
@@ -0,0 +1 @@
+dummy content
diff --git "a/path/with\nnewline" "b/path/with\nnewline"
new file mode 100644
index 0000000000000000000000000000000000000000..eaf5f7510320b6a327fb308379de2f94d8859a54
--- /dev/null
+++ "b/path/with\nnewline"
@@ -0,0 +1 @@
+dummy content
diff --git a/path/with spaces b/path/with spaces
new file mode 100644
index 0000000000000000000000000000000000000000..eaf5f7510320b6a327fb308379de2f94d8859a54
--- /dev/null
+++ b/path/with spaces
@@ -0,0 +1 @@
+dummy content
diff --git a/path/with-question-mark? b/path/with-question-mark?
new file mode 100644
index 0000000000000000000000000000000000000000..eaf5f7510320b6a327fb308379de2f94d8859a54
--- /dev/null
+++ b/path/with-question-mark?
@@ -0,0 +1 @@
+dummy content
diff --git "a/path/¯\\_(ツ)_|¯" "b/path/¯\\_(ツ)_|¯"
new file mode 100644
index 0000000000000000000000000000000000000000..eaf5f7510320b6a327fb308379de2f94d8859a54
--- /dev/null
+++ "b/path/¯\\_(ツ)_|¯"
@@ -0,0 +1 @@
+dummy content
diff --git "a/path/\360\237\222\251.txt" "b/path/\360\237\222\251.txt"
new file mode 100644
index 0000000000000000000000000000000000000000..eaf5f7510320b6a327fb308379de2f94d8859a54
--- /dev/null
+++ "b/path/\360\237\222\251.txt"
@@ -0,0 +1 @@
+dummy content
diff --git "a/path/\200-invalid-unicode-path.txt" "b/path/\200-invalid-unicode-path.txt"
new file mode 100644
index 0000000000000000000000000000000000000000..eaf5f7510320b6a327fb308379de2f94d8859a54
--- /dev/null
+++ "b/path/\200-invalid-unicode-path.txt"
@@ -0,0 +1 @@
+dummy content
diff --git a/a/with spaces b/b/with some spaces
similarity index 100%
rename from a/with spaces
rename to b/with some spaces
diff --git a/a/ending in a space b/b/ending with space
similarity index 100%
rename from a/ending in a space
rename to b/ending with space
diff --git "a/a/\"with-quotes\"" "b/b/\"with even more quotes\""
similarity index 100%
rename from "a/\"with-quotes\""
rename to "b/\"with even more quotes\""
@@ -0,0 +1 @@
:100755 100755 f4567df37451b230b1381b1bc9c2bcad76e08a3c 736bd596a36924d30b480942e9475ce0d734fa0d M rps
@@ -0,0 +1,12 @@
commit 2524c44334a8ba6b2ab8f3f0a478f04c5b073cc8
tree e126e7b4203dadf083f5eb8e2f34c255b51d8bee
parent d789e23b9ea8d90221d13c46f7c228d729385f92
author Michael Trier <mtrier@gmail.com> 1229389391 -0500
committer Michael Trier <mtrier@gmail.com> 1229389391 -0500
Renamed AUTHORS to CONTRIBUTORS because it's cooler.
diff --git a/AUTHORS b/CONTRIBUTORS
similarity index 100%
rename from Jérôme
rename to müller
@@ -0,0 +1 @@
:100644 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 R100 this that
@@ -0,0 +1,3 @@
634396b2f541a9f2d58b00be1a07f0c358b999b3
18 29 a.txt
5 0 b.txt
@@ -0,0 +1,46 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = git://gitorious.org/~byron/git-python/byrons-clone.git
pushurl = git@gitorious.org:~byron/git-python/byrons-clone.git
# a tab indented section header
[branch "master"]
remote = origin
merge = refs/heads/master
# an space indented section header
[remote "mainline"]
# space indented comment
url = git://gitorious.org/git-python/mainline.git
fetch = +refs/heads/*:refs/remotes/mainline/*
[remote "MartinMarcher"]
# tab indented comment
url = git://gitorious.org/~martin.marcher/git-python/serverhorror.git
fetch = +refs/heads/*:refs/remotes/MartinMarcher/*
# can handle comments - the section name is supposed to be stripped
# causes stock git-config puke
[ gui ]
geometry = 1316x820+219+243 207 192
[branch "mainline_performance"]
remote = mainline
merge = refs/heads/master
# section with value defined before include to be overriden
[sec]
var0 = value0_main
[include]
path = doesntexist.cfg
# field should be 'path' so abspath should be ignored
abspath = /usr/bin/foodoesntexist.bar
path = /usr/bin/foodoesntexist.bar
# should be relative to the path of this config file
path = ./git_config-inc.cfg
# and defined after include. According to the documentation
# and behavior of git config, this should be the value since
# inclusions should be processed immediately
[sec]
var1 = value1_main
@@ -0,0 +1,5 @@
[sec]
var0 = value0_included
var1 = value1_included
[diff]
tool = diff_included
@@ -0,0 +1,25 @@
# just a comment
[alias]
st = status
ci = commit
co = checkout
br = branch
[color]
branch = auto
diff = auto
interactive = auto
status = auto
[user]
name = Sebastian Thiel
email = byronimo@gmail.com
[core]
editor = vim
autocrlf = false
packedGitLimit = 1g
packedGitWindowSize = 512m
[pack]
windowMemory = 512m
[merge]
tool = meld
[diff]
tool = meld
@@ -0,0 +1,7 @@
[section0]
option0 = value0
[section1]
option1 = value1a
option1 = value1b
other_option1 = other_value1
@@ -0,0 +1,183 @@
[user]
name = Cody Veal
email = cveal05@gmail.com
[github]
user = cjhveal
[advice]
statusHints = false
[alias]
# add
a = add
aa = add --all
ap = add --patch
aliases = !git config --list | grep 'alias\\.' | sed 's/alias\\.\\([^=]*\\)=\\(.*\\)/\\1\\\t => \\2/' | sort
# branch
br = branch
branches = branch -av
cp = cherry-pick
diverges = !bash -c 'diff -u <(git rev-list --first-parent "${1}") <(git rev-list --first-parent "${2:-HEAD}"g | sed -ne \"s/^ //p\" | head -1' -
track = checkout -t
nb = checkout -b
# commit
amend = commit --amend -C HEAD
c = commit
ca = commit --amend
cm = commit --message
msg = commit --allow-empty -m
co = checkout
# diff
d = diff --color-words # diff by word
ds = diff --staged --color-words
dd = diff --color-words=. # diff by char
dds = diff --staged --color-words=.
dl = diff # diff by line
dls = diff --staged
h = help
# log
authors = "!git log --pretty=format:%aN | sort | uniq -c | sort -rn"
lc = log ORIG_HEAD.. --stat --no-merges
lg = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset' --abbrev-commit --date=relative
lol = log --graph --decorate --pretty=oneline --abbrev-commit
lola = log --graph --decorate --pretty=oneline --abbrev-commit --all
# merge
m = merge
mm = merge --no-ff
ours = "!f() { git checkout --ours $@ && git add $@; }; f"
theirs = "!f() { git checkout --theirs $@ && git add $@; }; f"
# push/pull
l = pull
p = push
sync = !git pull && git push
# remotes
prune-remotes = "!for remote in `git remote`; do git remote prune $remote; done"
r = remote
# rebase
rb = rebase
rba = rebase --abort
rbc = rebase --continue
rbs = rebase --skip
# reset
rh = reset --hard
rhh = reset HEAD --hard
uncommit = reset --soft HEAD^
unstage = reset HEAD --
unpush = push -f origin HEAD^:master
# stash
ss = stash
sl = stash list
sp = stash pop
sd = stash drop
snapshot = !git stash save "snapshot: $(date)" && git stash apply "stash@{0}"
# status
s = status --short --branch
st = status
# submodule
sm = submodule
sma = submodule add
smu = submodule update --init
pup = !git pull && git submodule init && git submodule update
# file level ignoring
assume = update-index --assume-unchanged
unassume = update-index --no-assume-unchanged
assumed = "!git ls-files -v | grep ^h | cut -c 3-"
[apply]
whitespace = fix
[color]
ui = auto
[color "branch"]
current = yellow reverse
local = yellow
remote = green
[color "diff"]
meta = yellow
frag = magenta
old = red bold
new = green bold
whitespace = red reverse
[color "status"]
added = green
changed = yellow
untracked = cyan
[core]
editor = /usr/bin/vim
excludesfile = ~/.gitignore_global
attributesfile = ~/.gitattributes
[diff]
renames = copies
mnemonicprefix = true
[diff "zip"]
textconv = unzip -c -a
[merge]
log = true
[merge "railsschema"]
name = newer Rails schema version
driver = "ruby -e '\n\
system %(git), %(merge-file), %(--marker-size=%L), %(%A), %(%O), %(%B)\n\
b = File.read(%(%A))\n\
b.sub!(/^<+ .*\\nActiveRecord::Schema\\.define.:version => (\\d+). do\\n=+\\nActiveRecord::Schema\\.define.:version => (\\d+). do\\n>+ .*/) do\n\
%(ActiveRecord::Schema.define(:version => #{[$1, $2].max}) do)\n\
end\n\
File.open(%(%A), %(w)) {|f| f.write(b)}\n\
exit 1 if b.include?(%(<)*%L)'"
[merge "gemfilelock"]
name = relocks the gemfile.lock
driver = bundle lock
[pager]
color = true
[push]
default = upstream
[rerere]
enabled = true
[url "git@github.com:"]
insteadOf = "gh:"
pushInsteadOf = "github:"
pushInsteadOf = "git://github.com/"
[url "git://github.com/"]
insteadOf = "github:"
[url "git@gist.github.com:"]
insteadOf = "gst:"
pushInsteadOf = "gist:"
pushInsteadOf = "git://gist.github.com/"
[url "git://gist.github.com/"]
insteadOf = "gist:"
[url "git@heroku.com:"]
insteadOf = "heroku:"
@@ -0,0 +1,4 @@
[color]
ui
[core]
filemode = true
@@ -0,0 +1 @@
gitdir: ./.real
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
100644 blob 81d2c27608b352814cbe979a6acd678d30219678 History.txt
100644 blob 641972d82c6d1b51122274ae8f6a0ecdfb56ee22 Manifest.txt
100644 blob 8b1e02c0fb554eed2ce2ef737a68bb369d7527df README.txt
100644 blob 735d7338b7cb208563aa282f0376c5c4049453a7 Rakefile
040000 tree c3d07b0083f01a6e1ac969a0f32b8d06f20c62e5 bin
040000 tree aa06ba24b4e3f463b3c4a85469d0fb9e5b421cf8 lib
040000 tree 650fa3f0c17f1edb4ae53d8dcca4ac59d86e6c44 test
@@ -0,0 +1,2 @@
100644 blob aa94e396335d2957ca92606f909e53e7beaf3fbb grit.rb
040000 tree 34868e6e7384cb5ee51c543a8187fdff2675b5a7 grit
@@ -0,0 +1,3 @@
040000 tree 2afb47bcedf21663580d5e6d2f406f08f3f65f19 foo
160000 commit d35b34c6e931b9da8f6941007a92c9c9a9b0141a bar
040000 tree f623ee576a09ca491c4a27e48c0dfe04be5f4a2e baz
@@ -0,0 +1,460 @@
501bf602abea7d21c3dbb409b435976e92033145 82b8902e033430000481eb355733cd7065342037 Sebastian Thiel <byronimo@gmail.com> 1270634931 +0200 commit: Used this release for a first beta of the 0.2 branch of development
82b8902e033430000481eb355733cd7065342037 69361d96a59381fde0ac34d19df2d4aff05fb9a9 Sebastian Thiel <byronimo@gmail.com> 1271229940 +0200 commit: conf.py: Adjusted version to match with the actual version
69361d96a59381fde0ac34d19df2d4aff05fb9a9 69361d96a59381fde0ac34d19df2d4aff05fb9a9 Sebastian Thiel <byronimo@gmail.com> 1272612605 +0200 checkout: moving from 69361d96a59381fde0ac34d19df2d4aff05fb9a9 to integration
69361d96a59381fde0ac34d19df2d4aff05fb9a9 b75c3103a700ac65b6cd18f66e2d0a07cfc09797 Sebastian Thiel <byronimo@gmail.com> 1272612605 +0200 pull git://gitorious.org/git-python/mainline.git refs/merge-requests/14: Merge made by recursive.
b75c3103a700ac65b6cd18f66e2d0a07cfc09797 0d6ceabf5b90e7c0690360fc30774d36644f563c Sebastian Thiel <byronimo@gmail.com> 1272614223 +0200 commit: Added additional tz_offset testing in performance test to call it more often.
0d6ceabf5b90e7c0690360fc30774d36644f563c 69361d96a59381fde0ac34d19df2d4aff05fb9a9 Sebastian Thiel <byronimo@gmail.com> 1272614242 +0200 checkout: moving from integration to master
69361d96a59381fde0ac34d19df2d4aff05fb9a9 0d6ceabf5b90e7c0690360fc30774d36644f563c Sebastian Thiel <byronimo@gmail.com> 1272614247 +0200 merge integration: Fast-forward
0d6ceabf5b90e7c0690360fc30774d36644f563c 997b7611dc5ec41d0e3860e237b530f387f3524a Sebastian Thiel <byronimo@gmail.com> 1272874921 +0200 checkout: moving from master to 997b7611dc5ec41d0e3860e237b530f387f3524a
997b7611dc5ec41d0e3860e237b530f387f3524a 0d6ceabf5b90e7c0690360fc30774d36644f563c Sebastian Thiel <byronimo@gmail.com> 1272919096 +0200 checkout: moving from 997b7611dc5ec41d0e3860e237b530f387f3524a to master
22a0289972b365b7912340501b52ca3dd98be289 143b927307d46ccb8f1cc095739e9625c03c82ff Sebastian Thiel <byronimo@gmail.com> 1272988814 +0200 commit: TODO: Removed all entries but left a mesage about where to find the issuee on lighthouse.
143b927307d46ccb8f1cc095739e9625c03c82ff e41c727be8dbf8f663e67624b109d9f8b135a4ab Sebastian Thiel <byronimo@gmail.com> 1273140152 +0200 commit: README: Added mailing list and issue tracker information
c083f3d0b853e723d0d4b00ff2f1ec5f65f05cba c083f3d0b853e723d0d4b00ff2f1ec5f65f05cba Sebastian Thiel <byronimo@gmail.com> 1273522280 +0200 checkout: moving from c083f3d0b853e723d0d4b00ff2f1ec5f65f05cba to integration
c083f3d0b853e723d0d4b00ff2f1ec5f65f05cba de5bc8f7076c5736ef1efa57345564fbc563bd19 Sebastian Thiel <byronimo@gmail.com> 1273522570 +0200 commit: Handle filenames with embedded spaces when generating diffs
de5bc8f7076c5736ef1efa57345564fbc563bd19 8caeec1b15645fa53ec5ddc6e990e7030ffb7c5a Sebastian Thiel <byronimo@gmail.com> 1273529174 +0200 commit: IndexFile.add: Fixed incorrect path handling if path rewriting was desired and absolute paths were given
600fcbc1a2d723f8d51e5f5ab6d9e4c389010e1c de5bc8f7076c5736ef1efa57345564fbc563bd19 Sebastian Thiel <byronimo@gmail.com> 1274808939 +0200 checkout: moving from master to master~2
de5bc8f7076c5736ef1efa57345564fbc563bd19 600fcbc1a2d723f8d51e5f5ab6d9e4c389010e1c Sebastian Thiel <byronimo@gmail.com> 1274808999 +0200 checkout: moving from de5bc8f7076c5736ef1efa57345564fbc563bd19 to master
600fcbc1a2d723f8d51e5f5ab6d9e4c389010e1c c083f3d0b853e723d0d4b00ff2f1ec5f65f05cba Sebastian Thiel <byronimo@gmail.com> 1274809635 +0200 checkout: moving from master to HEAD~3
c083f3d0b853e723d0d4b00ff2f1ec5f65f05cba 600fcbc1a2d723f8d51e5f5ab6d9e4c389010e1c Sebastian Thiel <byronimo@gmail.com> 1274809694 +0200 checkout: moving from c083f3d0b853e723d0d4b00ff2f1ec5f65f05cba to master
600fcbc1a2d723f8d51e5f5ab6d9e4c389010e1c 1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af Sebastian Thiel <byronimo@gmail.com> 1274811103 +0200 commit: diff: by limiting the splitcount to 5, a subtle bug was introduced as the newline at the end of the split line was not split away automatically. Added test for this, and the trivial fix
1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af 17af1f64d5f1e62d40e11b75b1dd48e843748b49 Sebastian Thiel <byronimo@gmail.com> 1274877948 +0200 commit: BlockingLockFile: added sanity check that raises IOError if the directory containing the lock was removed. This is unlikely to happen in a production envrironment, but may happen during testing, as folders are moved/deleted once the test is complete. Daemons might still be waiting for something, and they should be allowed to terminate instead of waiting for a possibly long time
17af1f64d5f1e62d40e11b75b1dd48e843748b49 34ba8ffba0b3b4d21da7bcea594cc3631e422142 Sebastian Thiel <byronimo@gmail.com> 1274906080 +0200 commit: refs: a Reference can now be created by assigning a commit or object (for convenience)
34ba8ffba0b3b4d21da7bcea594cc3631e422142 11dc82538cc1ebb537c866c8e76146e384cdfe24 Sebastian Thiel <byronimo@gmail.com> 1274906333 +0200 commit: refs: a Reference can now be created by assigning a commit or object (for convenience)
11dc82538cc1ebb537c866c8e76146e384cdfe24 34ba8ffba0b3b4d21da7bcea594cc3631e422142 Sebastian Thiel <byronimo@gmail.com> 1274906338 +0200 HEAD~1: updating HEAD
34ba8ffba0b3b4d21da7bcea594cc3631e422142 de84cbdd0f9ef97fcd3477b31b040c57192e28d9 Sebastian Thiel <byronimo@gmail.com> 1274906431 +0200 commit (amend): refs: a Reference can now be created by assigning a commit or object (for convenience)
de84cbdd0f9ef97fcd3477b31b040c57192e28d9 ecf37a1b4c2f70f1fc62a6852f40178bf08b9859 Sebastian Thiel <byronimo@gmail.com> 1274910053 +0200 commit: index: index-add fixed to always append a newline after each item. In git has unified its way it reads from stdin, now it wants all items to be terminated by a newline usually. Previously, it could have been that it really didn't want to have a termination character when the last item was written to the file. Bumped the minimum requirements to 1.7.0 to be sure it is working as I think it will.
ecf37a1b4c2f70f1fc62a6852f40178bf08b9859 1ee2afb00afaf77c883501eac8cd614c8229a444 Sebastian Thiel <byronimo@gmail.com> 1274914700 +0200 commit: cmd: By default, on linux, the parent file handles will be closed to leave the child less cluttered, and make it easier to debug as it will only have the file descriptors we set. It appears to be more stable regarding the stdin-is-closed-but-child-doesn't-realize-this issue
1ee2afb00afaf77c883501eac8cd614c8229a444 bd45e9267ab0d3f37e59ecc8b87d0ad19abad4ad Sebastian Thiel <byronimo@gmail.com> 1275324366 +0200 commit: gitcmd: may now receive extra keyword arguments to be passed directly to the subproces.Popen invocation. It could be used to pass custom environments, without changing the own one
bd45e9267ab0d3f37e59ecc8b87d0ad19abad4ad 6d9b1f4f9fa8c9f030e3207e7deacc5d5f8bba4e Sebastian Thiel <byronimo@gmail.com> 1275324409 +0200 commit (amend): gitcmd: may now receive extra keyword arguments to be passed directly to the subproces.Popen invocation. It could be used to pass custom environments, without changing the own one (#26)
6d9b1f4f9fa8c9f030e3207e7deacc5d5f8bba4e 6d9b1f4f9fa8c9f030e3207e7deacc5d5f8bba4e Sebastian Thiel <byronimo@gmail.com> 1275417756 +0200 checkout: moving from master to commit
6d9b1f4f9fa8c9f030e3207e7deacc5d5f8bba4e 14212649c0c48d0a7e5a83430873cae20aad4c83 Sebastian Thiel <byronimo@gmail.com> 1275432496 +0200 commit: commit: initial version of commit_from_tree which could create commit objects if it could serialize itself
14212649c0c48d0a7e5a83430873cae20aad4c83 7f6aa55077819e04dace82bc3ffbdea641b3e9ce Sebastian Thiel <byronimo@gmail.com> 1275432507 +0200 commit: commit: initial version of commit_from_tree which could create commit objects if it could serialize itself
7f6aa55077819e04dace82bc3ffbdea641b3e9ce 14212649c0c48d0a7e5a83430873cae20aad4c83 Sebastian Thiel <byronimo@gmail.com> 1275432513 +0200 HEAD~1: updating HEAD
14212649c0c48d0a7e5a83430873cae20aad4c83 1a0ec7154ea961d68ecfd4dec50f9fc1718686a2 Sebastian Thiel <byronimo@gmail.com> 1275433336 +0200 commit (amend): commit: initial version of commit_from_tree which could create commit objects if it could serialize itself
1a0ec7154ea961d68ecfd4dec50f9fc1718686a2 df0892351a394d768489b5647d47b73c24d3ef5f Sebastian Thiel <byronimo@gmail.com> 1275433456 +0200 commit (amend): commit: initial version of commit_from_tree which could create commit objects if it could serialize itself
df0892351a394d768489b5647d47b73c24d3ef5f df0892351a394d768489b5647d47b73c24d3ef5f Sebastian Thiel <byronimo@gmail.com> 1275474032 +0200 checkout: moving from commit to odb
df0892351a394d768489b5647d47b73c24d3ef5f 0e88ee839eaa5966f0d652372247fd14d80f9bb3 Sebastian Thiel <byronimo@gmail.com> 1275474633 +0200 commit: commit: refactored existing code to decode commits from streams - unfortunately this involves a little bit more python involvement currently, so performance might be slightly worse than before atm
0e88ee839eaa5966f0d652372247fd14d80f9bb3 6d9b1f4f9fa8c9f030e3207e7deacc5d5f8bba4e Sebastian Thiel <byronimo@gmail.com> 1275474676 +0200 checkout: moving from odb to master
6d9b1f4f9fa8c9f030e3207e7deacc5d5f8bba4e 0e88ee839eaa5966f0d652372247fd14d80f9bb3 Sebastian Thiel <byronimo@gmail.com> 1275474732 +0200 checkout: moving from master to odb
0e88ee839eaa5966f0d652372247fd14d80f9bb3 714e42d6315806dff61d39d8750ef8b250fb8d82 Sebastian Thiel <byronimo@gmail.com> 1275475614 +0200 commit (amend): commit: refactored existing code to decode commits from streams - performance is slightly better
714e42d6315806dff61d39d8750ef8b250fb8d82 8c1a87d11df666d308d14e4ae7ee0e9d614296b6 Sebastian Thiel <byronimo@gmail.com> 1275475865 +0200 commit (amend): commit: refactored existing code to decode commits from streams - performance is slightly better
8c1a87d11df666d308d14e4ae7ee0e9d614296b6 6d9b1f4f9fa8c9f030e3207e7deacc5d5f8bba4e Sebastian Thiel <byronimo@gmail.com> 1275475911 +0200 checkout: moving from odb to master
6d9b1f4f9fa8c9f030e3207e7deacc5d5f8bba4e 8c1a87d11df666d308d14e4ae7ee0e9d614296b6 Sebastian Thiel <byronimo@gmail.com> 1275475929 +0200 checkout: moving from master to odb
8c1a87d11df666d308d14e4ae7ee0e9d614296b6 8c1a87d11df666d308d14e4ae7ee0e9d614296b6 Sebastian Thiel <byronimo@gmail.com> 1275476474 +0200 checkout: moving from odb to perftest
8c1a87d11df666d308d14e4ae7ee0e9d614296b6 4a25347d7f4c371345da2348ac6cceec7a143da2 Sebastian Thiel <byronimo@gmail.com> 1275476487 +0200 commit: Added commit-iteration test
4a25347d7f4c371345da2348ac6cceec7a143da2 4e1c89ec97ec90037583e85d0e9e71e9c845a19b Sebastian Thiel <byronimo@gmail.com> 1275488012 +0200 commit: Added performance testing foundation library, reworked existing performance tests to work on larger repositories
4e1c89ec97ec90037583e85d0e9e71e9c845a19b ae5a69f67822d81bbbd8f4af93be68703e730b37 Sebastian Thiel <byronimo@gmail.com> 1275489688 +0200 commit: commit: redesigned revlist and commit parsing, commits are always retrieved from their object information directly. This is faster, and resolves issues with the rev-list format and empty commit messages
ae5a69f67822d81bbbd8f4af93be68703e730b37 02004a7ea4d26dc45f194d3a34780a50634ef497 Sebastian Thiel <byronimo@gmail.com> 1275493157 +0200 commit: git.cmd: added test for stream section constraint used in git command, found bug of course which just didn't kick in yet
02004a7ea4d26dc45f194d3a34780a50634ef497 ae5a69f67822d81bbbd8f4af93be68703e730b37 Sebastian Thiel <byronimo@gmail.com> 1275493169 +0200 HEAD~1: updating HEAD
ae5a69f67822d81bbbd8f4af93be68703e730b37 538820055ce1bf9dd07ecda48210832f96194504 Sebastian Thiel <byronimo@gmail.com> 1275493189 +0200 commit: git.cmd: added test for stream section constraint used in git command, found bug of course which just didn't kick in yet
538820055ce1bf9dd07ecda48210832f96194504 282018b79cc8df078381097cb3aeb29ff56e83c6 Sebastian Thiel <byronimo@gmail.com> 1275502260 +0200 commit: Added first design and frame for object database. In a first step, loose objects will be written using our utilities, and certain object retrieval functionality moves into the GitObjectDatabase which is used by the repo instance
282018b79cc8df078381097cb3aeb29ff56e83c6 8c1a87d11df666d308d14e4ae7ee0e9d614296b6 Sebastian Thiel <byronimo@gmail.com> 1275502285 +0200 checkout: moving from perftest to odb
8c1a87d11df666d308d14e4ae7ee0e9d614296b6 282018b79cc8df078381097cb3aeb29ff56e83c6 Sebastian Thiel <byronimo@gmail.com> 1275502288 +0200 merge perftest: Fast-forward
282018b79cc8df078381097cb3aeb29ff56e83c6 8b86f9b399a8f5af792a04025fdeefc02883f3e5 Sebastian Thiel <byronimo@gmail.com> 1275511252 +0200 commit: initial version of loose object writing and simple cached object lookup appears to be working
8b86f9b399a8f5af792a04025fdeefc02883f3e5 6f8ce8901e21587cd2320562df412e05b5ab1731 Sebastian Thiel <byronimo@gmail.com> 1275515609 +0200 commit: added frame for object reading, including simple test
6f8ce8901e21587cd2320562df412e05b5ab1731 6d9b1f4f9fa8c9f030e3207e7deacc5d5f8bba4e Sebastian Thiel <byronimo@gmail.com> 1275549198 +0200 checkout: moving from odb to master
6d9b1f4f9fa8c9f030e3207e7deacc5d5f8bba4e e79999c956e2260c37449139080d351db4aa3627 Sebastian Thiel <byronimo@gmail.com> 1275549608 +0200 commit: git.cmd: moved hardcoded chunksize when duplicating stream data into easy-to-change class member variable
e79999c956e2260c37449139080d351db4aa3627 6f8ce8901e21587cd2320562df412e05b5ab1731 Sebastian Thiel <byronimo@gmail.com> 1275549707 +0200 checkout: moving from master to odb
6f8ce8901e21587cd2320562df412e05b5ab1731 e79999c956e2260c37449139080d351db4aa3627 Sebastian Thiel <byronimo@gmail.com> 1275550120 +0200 checkout: moving from odb to master
e79999c956e2260c37449139080d351db4aa3627 412632599479a8e5991a07ecb67bc52b85c60755 Sebastian Thiel <byronimo@gmail.com> 1275550524 +0200 commit: git.cmd: using communicate in the main branch of execution, which might not make a big difference, but perhaps its smarter about broken pipes.
412632599479a8e5991a07ecb67bc52b85c60755 25dca42bac17d511b7e2ebdd9d1d679e7626db5f Sebastian Thiel <byronimo@gmail.com> 1275550670 +0200 commit (amend): git.cmd: using communicate in the main branch of execution, which might not make a big difference, but perhaps its smarter about broken pipes.
25dca42bac17d511b7e2ebdd9d1d679e7626db5f 6f8ce8901e21587cd2320562df412e05b5ab1731 Sebastian Thiel <byronimo@gmail.com> 1275551315 +0200 checkout: moving from master to odb
6f8ce8901e21587cd2320562df412e05b5ab1731 38d59fc8ccccae8882fa48671377bf40a27915a7 Sebastian Thiel <byronimo@gmail.com> 1275575735 +0200 commit: odb: implemented loose object streaming, which is impossible to do efficiently considering that it copies string buffers all the time
38d59fc8ccccae8882fa48671377bf40a27915a7 26e138cb47dccc859ff219f108ce9b7d96cbcbcd Sebastian Thiel <byronimo@gmail.com> 1275582065 +0200 commit: odb: fixed streamed decompression reader ( specific tests would still be missing ) and added performance tests which are extremely promising
26e138cb47dccc859ff219f108ce9b7d96cbcbcd 4295787b65d4a85ac1e0e20741aa59ec19a97353 Sebastian Thiel <byronimo@gmail.com> 1275584658 +0200 commit: Added performance comparison to cgit ... and yes, git-python is faster :)
4295787b65d4a85ac1e0e20741aa59ec19a97353 4b4a514e51fbc7dc6ddcb27c188159d57b5d1fa9 Sebastian Thiel <byronimo@gmail.com> 1275590443 +0200 commit (amend): Added performance comparison to cgit ... and yes, git-python is faster :)
4b4a514e51fbc7dc6ddcb27c188159d57b5d1fa9 1e2b46138ba58033738a24dadccc265748fce2ca Sebastian Thiel <byronimo@gmail.com> 1275600034 +0200 commit: commit.create_from_tree now uses pure python implementation, fixed message parsing which truncated newlines although it was ilegitimate. Its up to the reader to truncate therse, nowhere in the git code I could find anyone adding newlines to commits where it is written
1e2b46138ba58033738a24dadccc265748fce2ca 1906ee4df9ae4e734288c5203cf79894dff76cab Sebastian Thiel <byronimo@gmail.com> 1275600429 +0200 commit: Fixed compatability issues with python 2.5, made sure all tests run
1906ee4df9ae4e734288c5203cf79894dff76cab b01ca6a3e4ae9d944d799743c8ff774e2a7a82b6 Sebastian Thiel <byronimo@gmail.com> 1275602940 +0200 commit: db: implemented GitObjectDB using the git command to make sure we can lookup everything. Next is to implement pack-file reading, then alternates which should allow to resolve everything
b01ca6a3e4ae9d944d799743c8ff774e2a7a82b6 b01ca6a3e4ae9d944d799743c8ff774e2a7a82b6 Sebastian Thiel <byronimo@gmail.com> 1275643039 +0200 checkout: moving from odb to asyncredesign
b01ca6a3e4ae9d944d799743c8ff774e2a7a82b6 a1e80445ad5cb6da4c0070d7cb8af89da3b0803b Sebastian Thiel <byronimo@gmail.com> 1275655275 +0200 commit: initial version of new odb design to facilitate a channel based multi-threading implementation of all odb functions
a1e80445ad5cb6da4c0070d7cb8af89da3b0803b e746f96bcc29238b79118123028ca170adc4ff0f Sebastian Thiel <byronimo@gmail.com> 1275664928 +0200 commit: Fixed implementation after design change to deal with it - all tests run, but next there will have to be more through testing
e746f96bcc29238b79118123028ca170adc4ff0f b01ca6a3e4ae9d944d799743c8ff774e2a7a82b6 Sebastian Thiel <byronimo@gmail.com> 1275664939 +0200 checkout: moving from asyncredesign to odb
b01ca6a3e4ae9d944d799743c8ff774e2a7a82b6 e746f96bcc29238b79118123028ca170adc4ff0f Sebastian Thiel <byronimo@gmail.com> 1275664943 +0200 merge asyncredesign: Fast-forward
e746f96bcc29238b79118123028ca170adc4ff0f 25dca42bac17d511b7e2ebdd9d1d679e7626db5f Sebastian Thiel <byronimo@gmail.com> 1275664990 +0200 checkout: moving from odb to master
25dca42bac17d511b7e2ebdd9d1d679e7626db5f 6fbb69306c0e14bacb8dcb92a89af27d3d5d631f Sebastian Thiel <byronimo@gmail.com> 1275665431 +0200 commit (merge): Merge branch 'odb'
6fbb69306c0e14bacb8dcb92a89af27d3d5d631f a243827ab3346e188e99db2f9fc1f916941c9b1a Sebastian Thiel <byronimo@gmail.com> 1275685591 +0200 commit: Implemented stream tests, found a bug on the way, slowly a test-framework for streams starts to show up, but its not yet there
a243827ab3346e188e99db2f9fc1f916941c9b1a 7c1169f6ea406fec1e26e99821e18e66437e65eb Sebastian Thiel <byronimo@gmail.com> 1275690001 +0200 commit: Removed compression flag from IStream and OStream types, as a valid object will always be compressed if generated by the system ( even future memory db's will compress it )
7c1169f6ea406fec1e26e99821e18e66437e65eb c69b6b979e3d6bd01ec40e75b92b21f7a391f0ca Sebastian Thiel <byronimo@gmail.com> 1275746174 +0200 commit: Added basic channel implementation including test
c69b6b979e3d6bd01ec40e75b92b21f7a391f0ca c69b6b979e3d6bd01ec40e75b92b21f7a391f0ca Sebastian Thiel <byronimo@gmail.com> 1275746191 +0200 checkout: moving from master to async
c69b6b979e3d6bd01ec40e75b92b21f7a391f0ca c69b6b979e3d6bd01ec40e75b92b21f7a391f0ca Sebastian Thiel <byronimo@gmail.com> 1275746194 +0200 checkout: moving from async to master
c69b6b979e3d6bd01ec40e75b92b21f7a391f0ca 7c1169f6ea406fec1e26e99821e18e66437e65eb Sebastian Thiel <byronimo@gmail.com> 1275746196 +0200 HEAD~1: updating HEAD
7c1169f6ea406fec1e26e99821e18e66437e65eb c69b6b979e3d6bd01ec40e75b92b21f7a391f0ca Sebastian Thiel <byronimo@gmail.com> 1275746213 +0200 checkout: moving from master to async
c69b6b979e3d6bd01ec40e75b92b21f7a391f0ca 65c9fe0baa579173afa5a2d463ac198d06ef4993 Sebastian Thiel <byronimo@gmail.com> 1275746839 +0200 commit: A code donation: Donating a worker thread implementation inclduding tests to Git-Python. I have the feeling it can do much good here :)
65c9fe0baa579173afa5a2d463ac198d06ef4993 50e469109eed3a752d9a1b0297f16466ad92f8d2 Sebastian Thiel <byronimo@gmail.com> 1275755186 +0200 commit: Initial pool design added, allowing for lazy channel based evaluation of inter-dependent tasks
50e469109eed3a752d9a1b0297f16466ad92f8d2 61138f2ece0cb864b933698174315c34a78835d1 Sebastian Thiel <byronimo@gmail.com> 1275760757 +0200 commit: Moved multiprocessing modules into own package, as they in fact have nothing to do with the object db. If that really works the way I want, it will become an own project, called async
61138f2ece0cb864b933698174315c34a78835d1 ab59f78341f1dd188aaf4c30526f6295c63438b1 Sebastian Thiel <byronimo@gmail.com> 1275760989 +0200 commit: Renamed mp to async, as this is a much better name for what is actually going on. The default implementation uses threads, which ends up being nothing more than async, as they are all locked down by internal and the global interpreter lock
ab59f78341f1dd188aaf4c30526f6295c63438b1 b72e2704022d889f116e49abf3e1e5d3e3192d3b Sebastian Thiel <byronimo@gmail.com> 1275778812 +0200 commit: Improved pool design and started rough implementation, top down to learn while going. Tests will be written soon for verification, its still quite theoretical
b72e2704022d889f116e49abf3e1e5d3e3192d3b ec28ad575ce1d7bb6a616ffc404f32bbb1af67b2 Sebastian Thiel <byronimo@gmail.com> 1275821305 +0200 commit: thread: adjusted worker thread not to provide an output queue anymore - this is handled by the task system
ec28ad575ce1d7bb6a616ffc404f32bbb1af67b2 b3cde0ee162b8f0cb67da981311c8f9c16050a62 Sebastian Thiel <byronimo@gmail.com> 1275840801 +0200 commit: First step of testing the pool - tasks have been separated into a new module including own tests, their design improved to prepare them for some specifics that would be needed for multiprocessing support
b3cde0ee162b8f0cb67da981311c8f9c16050a62 8d74950510bbd74aa06afe4ec4c19e4739462d6a Sebastian Thiel <byronimo@gmail.com> 1275851713 +0200 commit: Plenty of fixes in the chunking routine, made possible by a serialized chunking test. Next up, actual async processing
8d74950510bbd74aa06afe4ec4c19e4739462d6a 1b27292936c81637f6b9a7141dafaad1126f268e Sebastian Thiel <byronimo@gmail.com> 1275852711 +0200 commit (amend): Plenty of fixes in the chunking routine, made possible by a serialized chunking test. Next up, actual async processing
1b27292936c81637f6b9a7141dafaad1126f268e 867129e2950458ab75523b920a5e227e3efa8bbc Sebastian Thiel <byronimo@gmail.com> 1275858486 +0200 commit: channel.read: enhanced to be sure we don't run into non-atomicity issues related to our channel closed flag, which is the only way not to block forever on read(0) channels which were closed by a thread 'in the meanwhile'
867129e2950458ab75523b920a5e227e3efa8bbc 6335fe0abcedc99145dd1400509b7540568ac2cc Sebastian Thiel <byronimo@gmail.com> 1275860480 +0200 commit: pool: First version which works as expected in async mode. The task model is very simple still, but its getting there
6335fe0abcedc99145dd1400509b7540568ac2cc 320c5329995cc8d364a88ba83103e1db584410ce Sebastian Thiel <byronimo@gmail.com> 1275860784 +0200 commit (amend): pool: First version which works as expected in async mode. The task model is very simple still, but its getting there
320c5329995cc8d364a88ba83103e1db584410ce d759d0b97aaf5fd60a1df0ea0f60e67863a6c3d7 Sebastian Thiel <byronimo@gmail.com> 1275861899 +0200 commit (amend): pool: First version which works as expected in async mode. The task model is very simple still, but its getting there
d759d0b97aaf5fd60a1df0ea0f60e67863a6c3d7 6a252661c3bf4202a4d571f9c41d2afa48d9d75f Sebastian Thiel <byronimo@gmail.com> 1275861909 +0200 commit (amend): pool: First version which works as expected in async mode. Its just using a single task for now, but next up are dependent tasks
6a252661c3bf4202a4d571f9c41d2afa48d9d75f a8a448b7864e21db46184eab0f0a21d7725d074f Sebastian Thiel <byronimo@gmail.com> 1275899902 +0200 commit: pool.consumed_tasks: is now a queue to be thread safe, in preparation for multiple connected pools
a8a448b7864e21db46184eab0f0a21d7725d074f 856af48fbffaf1b935d513429afeb319e4795d2d Sebastian Thiel <byronimo@gmail.com> 1275905456 +0200 commit: changed scheduling and chunksize calculation in respect to the task.min_count. Previously, it would possibly not produce enough items in case T1 wants to produce less items than t2 needs ... in fact, it would work even then, committing this anyway
856af48fbffaf1b935d513429afeb319e4795d2d 619662a9138fd78df02c52cae6dc89db1d70a0e5 Sebastian Thiel <byronimo@gmail.com> 1275905984 +0200 commit (amend): changed scheduling and chunksize calculation in respect to the task.min_count, to fix theoretical option for a deadlock in serial mode, and unnecessary blocking in async mode
619662a9138fd78df02c52cae6dc89db1d70a0e5 8c3c271b0d6b5f56b86e3f177caf3e916b509b52 Sebastian Thiel <byronimo@gmail.com> 1275908735 +0200 commit: Added task order cache, and a lock to prevent us walking the graph while changing tasks
8c3c271b0d6b5f56b86e3f177caf3e916b509b52 edd9e23c766cfd51b3a6f6eee5aac0b791ef2fd0 Sebastian Thiel <byronimo@gmail.com> 1275923808 +0200 commit: added high-speed locking facilities, allowing our Queue to be faster, at least in tests, and with multiple threads. There is still an sync bug in regard to closed channels to be fixed, as the Task.set_done handling is incorrecft
edd9e23c766cfd51b3a6f6eee5aac0b791ef2fd0 583cd8807259a69fc01874b798f657c1f9ab7828 Sebastian Thiel <byronimo@gmail.com> 1275930764 +0200 commit: Moved pool utilities into util module, fixed critical issue that caused havok - lets call this a safe-state
583cd8807259a69fc01874b798f657c1f9ab7828 654e54d200135e665e07e9f0097d913a77f169da Sebastian Thiel <byronimo@gmail.com> 1275933662 +0200 commit: task: Fixed incorrect handling of channel closure. Performance is alright for up to 2 threads, but 4 are killing the queue
654e54d200135e665e07e9f0097d913a77f169da be06e87433685b5ea9cfcc131ab89c56cf8292f2 Sebastian Thiel <byronimo@gmail.com> 1275940847 +0200 commit: improved testing to test the actual async handling of the pool. there are still inconsistencies that need to be fixed, but it already improved, especially the 4-thread performance which now is as fast as the dual-threaded performance
be06e87433685b5ea9cfcc131ab89c56cf8292f2 be06e87433685b5ea9cfcc131ab89c56cf8292f2 Sebastian Thiel <byronimo@gmail.com> 1275945495 +0200 checkout: moving from async to stasks
be06e87433685b5ea9cfcc131ab89c56cf8292f2 223701e19722afb0f57fc0de6e366ade542efdc0 Sebastian Thiel <byronimo@gmail.com> 1275945637 +0200 commit: introduced a new counter keeping track of the scheduled tasks - this prevent unnecessary tasks to be scheduled as we keep track of how many items will be produced for the task at hand. This introduces additional locking, but performns well in multithreaded mode. Performance of the master queue is still a huge issue, its currently the limiting factor, as bypassing the master queue in serial moode gives 15x performance, wich is what I would need
223701e19722afb0f57fc0de6e366ade542efdc0 def0f73989047c4ddf9b11da05ad2c9c8e387331 Sebastian Thiel <byronimo@gmail.com> 1275946081 +0200 commit (amend): introduced a new counter keeping track of the scheduled tasks - this prevent unnecessary tasks to be scheduled as we keep track of how many items will be produced for the task at hand. This introduces additional locking, but performns well in multithreaded mode. Performance of the master queue is still a huge issue, its currently the limiting factor, as bypassing the master queue in serial moode gives 15x performance, wich is what I would need
def0f73989047c4ddf9b11da05ad2c9c8e387331 be06e87433685b5ea9cfcc131ab89c56cf8292f2 Sebastian Thiel <byronimo@gmail.com> 1275946086 +0200 checkout: moving from stasks to async
be06e87433685b5ea9cfcc131ab89c56cf8292f2 be06e87433685b5ea9cfcc131ab89c56cf8292f2 Sebastian Thiel <byronimo@gmail.com> 1275946311 +0200 checkout: moving from async to brute
be06e87433685b5ea9cfcc131ab89c56cf8292f2 293fa4de92c789d67de6a663d7b14a6897b14181 Sebastian Thiel <byronimo@gmail.com> 1275946483 +0200 commit: Removed qsize dependency when reading , now it puts onto the queue everytime someone reads. This does not appear very stable for now as one can, for some reason, deplete the channel, which can only happen if its closed before all tasks finish, which should already be fixed
293fa4de92c789d67de6a663d7b14a6897b14181 def0f73989047c4ddf9b11da05ad2c9c8e387331 Sebastian Thiel <byronimo@gmail.com> 1275946494 +0200 checkout: moving from brute to stasks
def0f73989047c4ddf9b11da05ad2c9c8e387331 e825f8b69760e269218b1bf1991018baf3c16b04 Sebastian Thiel <byronimo@gmail.com> 1275946688 +0200 commit: Channel now uses the AsyncQueue, boosting performance by factor 4, its a start
e825f8b69760e269218b1bf1991018baf3c16b04 898d47d1711accdfded8ee470520fdb96fb12d46 Sebastian Thiel <byronimo@gmail.com> 1275947226 +0200 commit: Task scheduled items lock now uses a dummy lock in serial mode, improving its performance considerably.
898d47d1711accdfded8ee470520fdb96fb12d46 be06e87433685b5ea9cfcc131ab89c56cf8292f2 Sebastian Thiel <byronimo@gmail.com> 1275947355 +0200 checkout: moving from stasks to async
be06e87433685b5ea9cfcc131ab89c56cf8292f2 3e2ba9c2028f21d11988558f3557905d21e93808 Sebastian Thiel <byronimo@gmail.com> 1275947360 +0200 merge stasks: Merge made by recursive.
3e2ba9c2028f21d11988558f3557905d21e93808 7c1169f6ea406fec1e26e99821e18e66437e65eb Sebastian Thiel <byronimo@gmail.com> 1275948503 +0200 checkout: moving from async to master
7c1169f6ea406fec1e26e99821e18e66437e65eb 3e2ba9c2028f21d11988558f3557905d21e93808 Sebastian Thiel <byronimo@gmail.com> 1275948509 +0200 checkout: moving from master to async
3e2ba9c2028f21d11988558f3557905d21e93808 3e2ba9c2028f21d11988558f3557905d21e93808 Sebastian Thiel <byronimo@gmail.com> 1275948864 +0200 checkout: moving from async to queue
3e2ba9c2028f21d11988558f3557905d21e93808 5d996892ac76199886ba3e2754ff9c9fac2456d6 Sebastian Thiel <byronimo@gmail.com> 1275949953 +0200 commit: test implementation of async-queue with everything stripped from it that didn't seem necessary - its a failure, something is wrong - performance not much better than the original one, its depending on the condition performance actually, which I don't get faster
5d996892ac76199886ba3e2754ff9c9fac2456d6 3e2ba9c2028f21d11988558f3557905d21e93808 Sebastian Thiel <byronimo@gmail.com> 1275949960 +0200 checkout: moving from queue to async
3e2ba9c2028f21d11988558f3557905d21e93808 5d996892ac76199886ba3e2754ff9c9fac2456d6 Sebastian Thiel <byronimo@gmail.com> 1275979377 +0200 checkout: moving from async to queue
5d996892ac76199886ba3e2754ff9c9fac2456d6 3e2ba9c2028f21d11988558f3557905d21e93808 Sebastian Thiel <byronimo@gmail.com> 1275979426 +0200 checkout: moving from queue to async
3e2ba9c2028f21d11988558f3557905d21e93808 5d996892ac76199886ba3e2754ff9c9fac2456d6 Sebastian Thiel <byronimo@gmail.com> 1275979446 +0200 checkout: moving from async to queue
5d996892ac76199886ba3e2754ff9c9fac2456d6 f32ef32960ae8aa8a20c00cd3f7e78b441ee664b Sebastian Thiel <byronimo@gmail.com> 1275986714 +0200 commit: both versions of the async queue still have trouble in certain situations, at least with my totally overwritten version of the condition - the previous one was somewhat more stable it seems
f32ef32960ae8aa8a20c00cd3f7e78b441ee664b 09c3f39ceb545e1198ad7a3f470d4ec896ce1add Sebastian Thiel <byronimo@gmail.com> 1275986721 +0200 commit (amend): both versions of the async queue still have trouble in certain situations, at least with my totally overwritten version of the condition - the previous one was somewhat more stable it seems. Nonetheless, this is the fastest version so far
09c3f39ceb545e1198ad7a3f470d4ec896ce1add 3776f7a766851058f6435b9f606b16766425d7ca Sebastian Thiel <byronimo@gmail.com> 1275996284 +0200 commit: The new channeldesign actually works, but it also shows that its located at the wrong spot. The channel is nothing more than an adapter allowing to read multiple items from a thread-safe queue, the queue itself though must be 'closable' for writing, or needs something like a writable flag.
3776f7a766851058f6435b9f606b16766425d7ca 53152a824f5186452504f0b68306d10ebebee416 Sebastian Thiel <byronimo@gmail.com> 1275999838 +0200 commit: queue: adjusted queue to be closable ( without own testing yet, except for the pool which runs it ) - its not yet stable, but should be solvable.
53152a824f5186452504f0b68306d10ebebee416 619c11787742ce00a0ee8f841cec075897873c79 Sebastian Thiel <byronimo@gmail.com> 1276008468 +0200 commit: Its getting better already - intermediate commit before further chaning the task class
619c11787742ce00a0ee8f841cec075897873c79 13dd59ba5b3228820841682b59bad6c22476ff66 Sebastian Thiel <byronimo@gmail.com> 1276010743 +0200 commit: task: now deletes itself once its done - for the test this doesn't change a thing as the task deletes itself too late - its time for a paradigm change, the task should be deleted with its RPoolChannel or explicitly by the user. The test needs to adapt, and shouldn't assume anything unless the RPoolChannel is gone
13dd59ba5b3228820841682b59bad6c22476ff66 e5c0002d069382db1768349bf0c5ff40aafbf140 Sebastian Thiel <byronimo@gmail.com> 1276014012 +0200 commit: Revised task deletion works well, adjusted test to be creating new tasks all the time instead of reusing its own one, it was somewhat hard to manage its state over time and could cause bugs. It works okay, but it occasionally hangs, it appears to be an empty queue, have to gradually put certain things back in, although in the current mode of operation, it should never have empty queues from the pool to the user
e5c0002d069382db1768349bf0c5ff40aafbf140 772b95631916223e472989b43f3a31f61e237f31 Sebastian Thiel <byronimo@gmail.com> 1276017933 +0200 commit: workerthread: adjusted to use a blocking queue, it will receive termination events only with its queue, with boosts performance into brigt green levels
772b95631916223e472989b43f3a31f61e237f31 3e2ba9c2028f21d11988558f3557905d21e93808 Sebastian Thiel <byronimo@gmail.com> 1276017957 +0200 checkout: moving from queue to async
3e2ba9c2028f21d11988558f3557905d21e93808 f78d4a28f307a9d7943a06be9f919304c25ac2d9 Sebastian Thiel <byronimo@gmail.com> 1276017963 +0200 merge queue: Merge made by recursive.
f78d4a28f307a9d7943a06be9f919304c25ac2d9 15941ca090a2c3c987324fc911bbc6f89e941c47 Sebastian Thiel <byronimo@gmail.com> 1276072452 +0200 commit: queue: fixed critical bug in the notify method, as it was not at all thread-safe, causing locks to be released multiple times. Now it runs very fast, and very stable apparently.
15941ca090a2c3c987324fc911bbc6f89e941c47 f2c8d26d3b25b864ad48e6de018757266b59f708 Sebastian Thiel <byronimo@gmail.com> 1276075717 +0200 commit: thread: fixed initialization problem if an empty iterable was handed in
f2c8d26d3b25b864ad48e6de018757266b59f708 2054561da184955c4be4a92f0b4fa5c5c1c01350 Sebastian Thiel <byronimo@gmail.com> 1276075884 +0200 commit: HSCondition: using a deck to store waiters, for further speedup
2054561da184955c4be4a92f0b4fa5c5c1c01350 1090701721888474d34f8a4af28ee1bb1c3fdaaa Sebastian Thiel <byronimo@gmail.com> 1276076141 +0200 commit: HSCondition: now deriving from deque, as the AsyncQeue does, to elimitate one more level of indirection. Clearly this not good from a design standpoint, as a Condition is no Deque, but it helps speeding things up which is what this is about. Could make it a hidden class to indicate how 'special' it is
1090701721888474d34f8a4af28ee1bb1c3fdaaa a988e6985849e4f6a561b4a5468d525c25ce74fe Sebastian Thiel <byronimo@gmail.com> 1276076725 +0200 commit: HSCondition: now gets a lock even in the single-notify case, as it was required due to the non-atomiciy of the invovled operation. Removed one level of indirection for the lock, by refraining from calling my own 'wrapper' methods, which brought it back to the performance it had before the locking was introduced for the n==1 case
a988e6985849e4f6a561b4a5468d525c25ce74fe 4e6bece08aea01859a232e99a1e1ad8cc1eb7d36 Sebastian Thiel <byronimo@gmail.com> 1276084911 +0200 commit: HSCondition: Fixed terrible bug which it inherited from its default python Condition implementation, related to the notify method not being treadsafe. Although I was aware of it, I missed the first check which tests for the size - the result could be incorrect if the whole method wasn't locked.
4e6bece08aea01859a232e99a1e1ad8cc1eb7d36 ffb5b95cb2cec5c5a79234dfc47c3fcf1f724101 Sebastian Thiel <byronimo@gmail.com> 1276087661 +0200 commit: Channel: Read method revised - now it really really doesn't block anymore, and it runs faster as well, about 2/3 of the performance we have when being in serial mode
ffb5b95cb2cec5c5a79234dfc47c3fcf1f724101 0974f8737a3c56a7c076f9d0b757c6cb106324fb Sebastian Thiel <byronimo@gmail.com> 1276087839 +0200 commit (amend): Channel: Read method revised - now it really really doesn't block anymore, and it runs faster as well, about 2/3 of the performance we have when being in serial mode
0974f8737a3c56a7c076f9d0b757c6cb106324fb 57a4e09294230a36cc874a6272c71757c48139f2 Sebastian Thiel <byronimo@gmail.com> 1276090187 +0200 commit: Channel: removed pseudoconstructor, which clearly improves the design and makes it easier to constomize
57a4e09294230a36cc874a6272c71757c48139f2 07996a1a1e53ffdd2680d4bfbc2f4059687859a5 Sebastian Thiel <byronimo@gmail.com> 1276090851 +0200 commit: task: removed scheduled task support, which at some point was introduced to improve performance, but which now hinders performance, besides being unnecessary ;)
07996a1a1e53ffdd2680d4bfbc2f4059687859a5 ea81f14dafbfb24d70373c74b5f8dabf3f2225d9 Sebastian Thiel <byronimo@gmail.com> 1276094301 +0200 commit: Channel: Callbacks reviewed - they are now part of Subclasses of the default channel implementation, one of which is used as base by the Pool Read channel, releasing it of the duty to call these itself. The write channel with callback subclass allows the transformation of the item to be written
ea81f14dafbfb24d70373c74b5f8dabf3f2225d9 365fb14ced88a5571d3287ff1698582ceacd80d6 Sebastian Thiel <byronimo@gmail.com> 1276095557 +0200 commit: task: redesigned write channel access to allow the task creator to set own write channels, possibly some with callbacks installed etc.. Pool.add_task will respect the users choice now, but provide defaults which are optimized for performance
365fb14ced88a5571d3287ff1698582ceacd80d6 257a8a9441fca9a9bc384f673ba86ef5c3f1715d Sebastian Thiel <byronimo@gmail.com> 1276111194 +0200 commit: test: prepared task dependency test, which already helped to find bug in the reference counting mechanism, causing references to the pool to be kepts via cycles
257a8a9441fca9a9bc384f673ba86ef5c3f1715d 3323464f85b986cba23176271da92a478b33ab9c Sebastian Thiel <byronimo@gmail.com> 1276122289 +0200 commit: messy first version of a properly working depth-first graph method, which allows the pool to work as expected. Many more tests need to be added, and there still is a problem with shutdown as sometimes it won't kill all threads, mainly because the process came up with worker threads started, which cannot be
3323464f85b986cba23176271da92a478b33ab9c 3323464f85b986cba23176271da92a478b33ab9c Sebastian Thiel <byronimo@gmail.com> 1276122375 +0200 checkout: moving from async to async
3323464f85b986cba23176271da92a478b33ab9c 257a8a9441fca9a9bc384f673ba86ef5c3f1715d Sebastian Thiel <byronimo@gmail.com> 1276122387 +0200 HEAD~1: updating HEAD
257a8a9441fca9a9bc384f673ba86ef5c3f1715d 3323464f85b986cba23176271da92a478b33ab9c Sebastian Thiel <byronimo@gmail.com> 1276122419 +0200 checkout: moving from async to taskdep
3323464f85b986cba23176271da92a478b33ab9c cfb278d74ad01f3f1edf5e0ad113974a9555038d Sebastian Thiel <byronimo@gmail.com> 1276157672 +0200 commit: InputChannelTask now has interface for properly handling the reading from the same and different pools
cfb278d74ad01f3f1edf5e0ad113974a9555038d 01eac1a959c1fa5894a86bf11e6b92f96762bdd8 Sebastian Thiel <byronimo@gmail.com> 1276164376 +0200 commit: Added more dependency task tests, especially the single-reads are not yet fully deterministic as tasks still run into the problem that they try to write into a closed channel, it was closed by one of their task-mates who didn't know someone else was still computing
01eac1a959c1fa5894a86bf11e6b92f96762bdd8 01eac1a959c1fa5894a86bf11e6b92f96762bdd8 Sebastian Thiel <byronimo@gmail.com> 1276166920 +0200 checkout: moving from taskdep to channel
01eac1a959c1fa5894a86bf11e6b92f96762bdd8 01eac1a959c1fa5894a86bf11e6b92f96762bdd8 Sebastian Thiel <byronimo@gmail.com> 1276167802 +0200 checkout: moving from channel to taskdep
01eac1a959c1fa5894a86bf11e6b92f96762bdd8 01eac1a959c1fa5894a86bf11e6b92f96762bdd8 Sebastian Thiel <byronimo@gmail.com> 1276169390 +0200 checkout: moving from taskdep to channel
01eac1a959c1fa5894a86bf11e6b92f96762bdd8 55e757928e493ce93056822d510482e4ffcaac2d Sebastian Thiel <byronimo@gmail.com> 1276173597 +0200 commit: channel: Changed design to be more logical - a channel now has any amount of readers and writers, a ready is not connected to its writer anymore. This changes the refcounting of course, which is why the auto-cleanup for the pool is currently broken.
55e757928e493ce93056822d510482e4ffcaac2d 7c36f3648e39ace752c67c71867693ce1eee52a3 Sebastian Thiel <byronimo@gmail.com> 1276177120 +0200 commit: Now tracking the amount of concurrent writers to assure the channel is closed only when there is no one else writing to it. This assures that all tasks can continue working, and put their results accordingly. Shutdown is still not working correctly, but that should be solvable as well. Its still not perfect though ...
7c36f3648e39ace752c67c71867693ce1eee52a3 c34343d0b714d2c4657972020afea034a167a682 Sebastian Thiel <byronimo@gmail.com> 1276177952 +0200 commit: tasks can now terminate faster when no items were read, without neglecting their duty to close the channel if required. Code is a little less maintainable now, but faster, it appears
c34343d0b714d2c4657972020afea034a167a682 4c2fa54d8122e9e5bc20a938ff8ccc5caf96dafe Sebastian Thiel <byronimo@gmail.com> 1276206673 +0200 commit: tasks can now terminate faster when no items were read, without neglecting their duty to close the channel if required. Code is a little less maintainable now, but faster, it appearsgst
4c2fa54d8122e9e5bc20a938ff8ccc5caf96dafe c34343d0b714d2c4657972020afea034a167a682 Sebastian Thiel <byronimo@gmail.com> 1276206682 +0200 HEAD~1: updating HEAD
c34343d0b714d2c4657972020afea034a167a682 fbe062bf6dacd3ad63dd827d898337fa542931ac Sebastian Thiel <byronimo@gmail.com> 1276206950 +0200 commit: Added dependency-task tests, and fixed plenty of ref-count related bugs, as well as concurrency issues. Now it works okay, but the thread-shutdown is still an issue, as it causes incorrect behaviour making the tests fail. Its good, as it hints at additional issues that need to be solved. There is just a little more left on the feature side, but its nearly there
fbe062bf6dacd3ad63dd827d898337fa542931ac 6d1212e8c412b0b4802bc1080d38d54907db879d Sebastian Thiel <byronimo@gmail.com> 1276249921 +0200 commit: IMPORTANT: sometimes, when notifying waiters by releasing their lock, the lock is not actually released or they are not actually notifyied, staying in a beautysleep. This glitch is probably caused by some detail not treated correctly in the thread python module, which is something we cannot fix. It works most of the time as expected though - maybe some cleanup is not done correctly which causes this
6d1212e8c412b0b4802bc1080d38d54907db879d 01eac1a959c1fa5894a86bf11e6b92f96762bdd8 Sebastian Thiel <byronimo@gmail.com> 1276249936 +0200 checkout: moving from channel to taskdep
01eac1a959c1fa5894a86bf11e6b92f96762bdd8 18b14ade522c71000a69ff80569eda7335d6f64c Sebastian Thiel <byronimo@gmail.com> 1276249944 +0200 merge channel: Merge made by recursive.
18b14ade522c71000a69ff80569eda7335d6f64c 583e6a25b0d891a2f531a81029f2bac0c237cbf9 Sebastian Thiel <byronimo@gmail.com> 1276258825 +0200 commit (amend): Merge branch 'channel' into taskdep
583e6a25b0d891a2f531a81029f2bac0c237cbf9 5ff864138cd1e680a78522c26b583639f8f5e313 Sebastian Thiel <byronimo@gmail.com> 1276259871 +0200 commit: test.async: split test_pool up into task implenetations and related utilities, as well as the tests themselves. File became too large
5ff864138cd1e680a78522c26b583639f8f5e313 18e3252a1f655f09093a4cffd5125342a8f94f3b Sebastian Thiel <byronimo@gmail.com> 1276261131 +0200 commit: Finished dependent task testing according to the features we would currently like to see
18e3252a1f655f09093a4cffd5125342a8f94f3b 257a8a9441fca9a9bc384f673ba86ef5c3f1715d Sebastian Thiel <byronimo@gmail.com> 1276261137 +0200 checkout: moving from taskdep to async
257a8a9441fca9a9bc384f673ba86ef5c3f1715d f606937a7a21237c866efafcad33675e6539c103 Sebastian Thiel <byronimo@gmail.com> 1276261142 +0200 merge taskdep: Merge made by recursive.
f606937a7a21237c866efafcad33675e6539c103 f606937a7a21237c866efafcad33675e6539c103 Sebastian Thiel <byronimo@gmail.com> 1276261390 +0200 checkout: moving from async to cleanup
f606937a7a21237c866efafcad33675e6539c103 02de401bc0b5ab5b97736265b18c7d1d6f53b9c1 Sebastian Thiel <byronimo@gmail.com> 1276266327 +0200 commit: Improved shutdown handling - although its impossible to prevent some stderr printing thanks to the underlying threading implementation, we can at least make sure that the interpreter doesn't block during shutdown. Now it appears to be running smoothly
02de401bc0b5ab5b97736265b18c7d1d6f53b9c1 1873db442dc7511fc2c92fbaeb8d998d3e62723d Sebastian Thiel <byronimo@gmail.com> 1276266595 +0200 commit (amend): Improved shutdown handling - although its impossible to prevent some stderr printing thanks to the underlying threading implementation, we can at least make sure that the interpreter doesn't block during shutdown. Now it appears to be running smoothly
1873db442dc7511fc2c92fbaeb8d998d3e62723d e14e3f143e7260de9581aee27e5a9b2645db72de Sebastian Thiel <byronimo@gmail.com> 1276267329 +0200 commit: Removed commented-out debug code and additional debug printings. Verified it works on py2.4, 2.5 and 2.6
e14e3f143e7260de9581aee27e5a9b2645db72de f606937a7a21237c866efafcad33675e6539c103 Sebastian Thiel <byronimo@gmail.com> 1276267656 +0200 checkout: moving from cleanup to async
f606937a7a21237c866efafcad33675e6539c103 29eb123beb1c55e5db4aa652d843adccbd09ae18 Sebastian Thiel <byronimo@gmail.com> 1276267660 +0200 merge cleanup: Merge made by recursive.
29eb123beb1c55e5db4aa652d843adccbd09ae18 cac6e06cc9ef2903a15e594186445f3baa989a1a Sebastian Thiel <byronimo@gmail.com> 1276268324 +0200 commit: test_task: fixed import error, made all modules from x import * safe
cac6e06cc9ef2903a15e594186445f3baa989a1a a28942bdf01f4ddb9d0b5a0489bd6f4e101dd775 Sebastian Thiel <byronimo@gmail.com> 1276280001 +0200 commit: Added performance test, improved iterator task which will now be usable by default. It shows that there must be the notion of a producer, which can work if there are no items read
a28942bdf01f4ddb9d0b5a0489bd6f4e101dd775 9e7fbc06cbcb51efb8c88fedaeb257a435c0c162 Sebastian Thiel <byronimo@gmail.com> 1276334358 +0200 commit: Cleaned up channel design, Reader and Writer bases don't require a channel anymore, but are abstract.
9e7fbc06cbcb51efb8c88fedaeb257a435c0c162 be8955a0fbb77d673587974b763f17c214904b57 Sebastian Thiel <byronimo@gmail.com> 1276334369 +0200 commit (amend): Cleaned up channel design, Reader and Writer bases don't require a channel anymore, but are abstract.
be8955a0fbb77d673587974b763f17c214904b57 1d8a577ffc6ad7ce1465001ddebdc157aecc1617 Sebastian Thiel <byronimo@gmail.com> 1276335670 +0200 commit: channel: cleaned up inheritance hierarchy, adding mixing for callback functionality - previously the callback functionality was bound to channel based readers/writers
1d8a577ffc6ad7ce1465001ddebdc157aecc1617 eded3fb0c820761d51d462e0d96187371c1758dc Sebastian Thiel <byronimo@gmail.com> 1276339082 +0200 commit: task: improved naming of task types, improved pool test to be less dependent on starting with just the main thread
eded3fb0c820761d51d462e0d96187371c1758dc 7a0b79ee574999ecbc76696506352e4a5a0d7159 Sebastian Thiel <byronimo@gmail.com> 1276339207 +0200 commit (amend): task: improved naming of task types, improved pool test to be less dependent on starting with just the main thread
7a0b79ee574999ecbc76696506352e4a5a0d7159 7c1169f6ea406fec1e26e99821e18e66437e65eb Sebastian Thiel <byronimo@gmail.com> 1276339273 +0200 checkout: moving from async to master
7c1169f6ea406fec1e26e99821e18e66437e65eb f91495e271597034226f1b9651345091083172c4 Sebastian Thiel <byronimo@gmail.com> 1276339280 +0200 merge async: Merge made by recursive.
f91495e271597034226f1b9651345091083172c4 5c631ca192848fed3068b31b1389cd92a0c0cdca Sebastian Thiel <byronimo@gmail.com> 1276340638 +0200 commit: Removed async from this repository, put it into own one which now comes in as external, using a git-submodule
5c631ca192848fed3068b31b1389cd92a0c0cdca f91495e271597034226f1b9651345091083172c4 Sebastian Thiel <byronimo@gmail.com> 1276345979 +0200 HEAD~1: updating HEAD
f91495e271597034226f1b9651345091083172c4 86ea63504f3e8a74cfb1d533be9d9602d2d17e27 Sebastian Thiel <byronimo@gmail.com> 1276346049 +0200 commit: Removed async from tree
86ea63504f3e8a74cfb1d533be9d9602d2d17e27 6c1faef799095f3990e9970bc2cb10aa0221cf9c Sebastian Thiel <byronimo@gmail.com> 1276356043 +0200 commit: Removed odb from project, it is now used as a submodule named gitdb, which was added instead
6c1faef799095f3990e9970bc2cb10aa0221cf9c 28ed48c93f4cc8b6dd23c951363e5bd4e6880992 Sebastian Thiel <byronimo@gmail.com> 1276503381 +0200 commit: Implemented initial version of tree serialization which appears to work according to a simple test
28ed48c93f4cc8b6dd23c951363e5bd4e6880992 fe5289ed8311fecf39913ce3ae86b1011eafe5f7 Sebastian Thiel <byronimo@gmail.com> 1276506168 +0200 commit: tree now uses less memory for its cache as it stores the bare deserialized information - this also speeds up later serialization after changes. its clear though that retrieving actual objects is slower currently as these are not cached anymore. Its worth thinking about moving these encoding, decoding routines to gitdb
fe5289ed8311fecf39913ce3ae86b1011eafe5f7 f8dabbf4f92a7023181777e9d40355562474f71a Sebastian Thiel <byronimo@gmail.com> 1276512508 +0200 commit: tree: added TreeModifier, allowing to adjust existing trees safely and or fast, while staying compatible with serialization which requires it to be sorted
f8dabbf4f92a7023181777e9d40355562474f71a d9240918aa03e49feabe43af619019805ac76786 Sebastian Thiel <byronimo@gmail.com> 1276512707 +0200 commit (amend): tree: added TreeModifier, allowing to adjust existing trees safely and or fast, while staying compatible with serialization which requires it to be sorted
d9240918aa03e49feabe43af619019805ac76786 d9240918aa03e49feabe43af619019805ac76786 Sebastian Thiel <byronimo@gmail.com> 1276520481 +0200 checkout: moving from master to index
d9240918aa03e49feabe43af619019805ac76786 af32b6e0ad4ab244dc70a5ade0f8a27ab45942f8 Sebastian Thiel <byronimo@gmail.com> 1276524270 +0200 commit: index: split index file into multiple files of a single package. This didn't reduce the file size as much as I would have liked, but certainly is a start for further 'outsourcing'
af32b6e0ad4ab244dc70a5ade0f8a27ab45942f8 0ad4af53d4704489f2fd8bd067241bf12c8ee35a Sebastian Thiel <byronimo@gmail.com> 1276525421 +0200 commit: Implemented the serializable interface - by refactoring code
0ad4af53d4704489f2fd8bd067241bf12c8ee35a abaefc59a7f2986ab344a65ef2a3653ce7dd339f Sebastian Thiel <byronimo@gmail.com> 1276527582 +0200 commit (amend): Implemented the serializable interface - by refactoring code
abaefc59a7f2986ab344a65ef2a3653ce7dd339f d9240918aa03e49feabe43af619019805ac76786 Sebastian Thiel <byronimo@gmail.com> 1276527605 +0200 checkout: moving from index to master
d9240918aa03e49feabe43af619019805ac76786 38b3cfb9b24a108e0720f7a3f8d6355f7e0bb1a9 Sebastian Thiel <byronimo@gmail.com> 1276527612 +0200 merge index: Merge made by recursive.
38b3cfb9b24a108e0720f7a3f8d6355f7e0bb1a9 c9dbf201b4f0b3c2b299464618cb4ecb624d272c Sebastian Thiel <byronimo@gmail.com> 1276529105 +0200 commit: Moved small types that had their own module into the utils module
c9dbf201b4f0b3c2b299464618cb4ecb624d272c 45e87305bd4f050c2d0309c32fe5de499fc38df3 Sebastian Thiel <byronimo@gmail.com> 1276554725 +0200 commit: Reimplemented Lock handling to be conforming to the git lock protocol, which is actually more efficient than the previous implementation
45e87305bd4f050c2d0309c32fe5de499fc38df3 06590aee389f4466e02407f39af1674366a74705 Sebastian Thiel <byronimo@gmail.com> 1276555536 +0200 commit (amend): Reimplemented Lock handling to be conforming to the git lock protocol, which is actually more efficient than the previous implementation
06590aee389f4466e02407f39af1674366a74705 1d2307532d679393ae067326e4b6fa1a2ba5cc06 Sebastian Thiel <byronimo@gmail.com> 1276556905 +0200 commit: Moved LockedFD and its test into the gitdb project
1d2307532d679393ae067326e4b6fa1a2ba5cc06 e837b901dcfac82e864f806c80f4a9cbfdb9c9f3 Sebastian Thiel <byronimo@gmail.com> 1276607908 +0200 commit: Move LazyMixin type to gitdb, index reading now uses file_contents_ro from gitdb as well
e837b901dcfac82e864f806c80f4a9cbfdb9c9f3 b82dbf538ac0d03968a0f5b7e2318891abefafaa Sebastian Thiel <byronimo@gmail.com> 1276870827 +0200 commit: GitCmd implementation of gitdb base moved to git-python where it belongs. Previously it was located in gitdb, which doesn't have any facilities to use the git command
b82dbf538ac0d03968a0f5b7e2318891abefafaa f164627a85ed7b816759871a76db258515b85678 Sebastian Thiel <byronimo@gmail.com> 1277057845 +0200 commit: db: added pure python git database
f164627a85ed7b816759871a76db258515b85678 ac62760c52abf28d1fd863f0c0dd48bc4a23d223 Sebastian Thiel <byronimo@gmail.com> 1277117506 +0200 commit: index.add: now uses gitdb.store functionality instead of git-hash-file. The python version is about as fast, but could support multithreading using async
ac62760c52abf28d1fd863f0c0dd48bc4a23d223 0fdf6c3aaff49494c47aaeb0caa04b3016e10a26 Sebastian Thiel <byronimo@gmail.com> 1277127929 +0200 commit: index: Entries are now using flags internally, instead of reducing the flag information to just the stage ( just to be closer to the git-original )
0fdf6c3aaff49494c47aaeb0caa04b3016e10a26 0aeb491d3d8f53e07fb21f36251be4880170c5ab Sebastian Thiel <byronimo@gmail.com> 1277129321 +0200 commit: index.add does not need the git clt anymore
0aeb491d3d8f53e07fb21f36251be4880170c5ab 91725f0fc59aa05ef68ab96e9b29009ce84668a5 Sebastian Thiel <byronimo@gmail.com> 1277129385 +0200 commit (amend): index.add does not need the git clt anymore
91725f0fc59aa05ef68ab96e9b29009ce84668a5 91725f0fc59aa05ef68ab96e9b29009ce84668a5 Sebastian Thiel <byronimo@gmail.com> 1277132483 +0200 checkout: moving from master to writetree
91725f0fc59aa05ef68ab96e9b29009ce84668a5 1044116d25f0311033e0951d2ab30579bba4b051 Sebastian Thiel <byronimo@gmail.com> 1277144182 +0200 commit: index: put serialization methods into new 'fun' module, this makes the calls faster as it removes one level of indirection, and makes the main file smaller, improving maintainability
1044116d25f0311033e0951d2ab30579bba4b051 69dd8750be1fbf55010a738dc1ced4655e727f23 Sebastian Thiel <byronimo@gmail.com> 1277157937 +0200 commit: index.write_tree: initial version implemented, although its not yet working correctly, a test to explicitly compare the git version with the python implementation is still missing
69dd8750be1fbf55010a738dc1ced4655e727f23 cadce432d8ef07e7293f8b760342d717bd350671 Sebastian Thiel <byronimo@gmail.com> 1277188932 +0200 commit: intermediate commit, rollback
cadce432d8ef07e7293f8b760342d717bd350671 69dd8750be1fbf55010a738dc1ced4655e727f23 Sebastian Thiel <byronimo@gmail.com> 1277189427 +0200 HEAD~1: updating HEAD
69dd8750be1fbf55010a738dc1ced4655e727f23 d2d9197cfe5d3b43cb8aee182b2e65c73ef9ab7b Sebastian Thiel <byronimo@gmail.com> 1277193172 +0200 commit: Tree-Writing now works after fixing an off-by-one error
d2d9197cfe5d3b43cb8aee182b2e65c73ef9ab7b c4f49fb232acb2c02761a82acc12c4040699685d Sebastian Thiel <byronimo@gmail.com> 1277201017 +0200 commit: index.write_tree: now uses MemoryDB, making tree handling more efficient as IO will only be done when required. A possible disadvantage though is that time is spent on compressing the trees, although only the raw data and their shas would theoretically be needed. On the other hand, compressing their data uses less memory. An optimal implementation would just sha the data, check for existance, and compress it to write it to the database right away. This would mean more specialized code though, introducing redundancy. If IStreams would know whether they contain compressed or uncompressed data, and if there was a method to get a sha from data, this would work nicely in the existing framework though
c4f49fb232acb2c02761a82acc12c4040699685d 91725f0fc59aa05ef68ab96e9b29009ce84668a5 Sebastian Thiel <byronimo@gmail.com> 1277201030 +0200 checkout: moving from writetree to master
91725f0fc59aa05ef68ab96e9b29009ce84668a5 778234d544b3f58dd415aaf10679d15b01a5281f Sebastian Thiel <byronimo@gmail.com> 1277201033 +0200 merge writetree: Merge made by recursive.
778234d544b3f58dd415aaf10679d15b01a5281f 778234d544b3f58dd415aaf10679d15b01a5281f Sebastian Thiel <byronimo@gmail.com> 1277209257 +0200 checkout: moving from master to fromtree
778234d544b3f58dd415aaf10679d15b01a5281f be97c4558992a437cde235aafc7ae2bd6df84ac8 Sebastian Thiel <byronimo@gmail.com> 1277234627 +0200 commit: Initial frame for implementing read_tree using pure python. As git-read-tree can do much more than we can ( and faster assumably ), the .new method is used to create new index instances from up to 3 trees.
be97c4558992a437cde235aafc7ae2bd6df84ac8 c0ef65b43688b1a4615a1e7332f6215f9a8abb19 Sebastian Thiel <byronimo@gmail.com> 1277245716 +0200 commit: Implemented simple tree merging and a simple test, more elaborate testing is in progress
c0ef65b43688b1a4615a1e7332f6215f9a8abb19 aea0243840a46021e6f77c759c960a06151d91c9 Sebastian Thiel <byronimo@gmail.com> 1277293745 +0200 commit: Added test for aggressive_tree_merge
aea0243840a46021e6f77c759c960a06151d91c9 1e2265a23ecec4e4d9ad60d788462e7f124f1bb7 Sebastian Thiel <byronimo@gmail.com> 1277300937 +0200 commit: fixed critical bug in traverse_trees_recursive, implemented IndexFile.new including simple test, it may be simple as the methods it uses are throroughly tested
1e2265a23ecec4e4d9ad60d788462e7f124f1bb7 778234d544b3f58dd415aaf10679d15b01a5281f Sebastian Thiel <byronimo@gmail.com> 1277300988 +0200 checkout: moving from fromtree to master
778234d544b3f58dd415aaf10679d15b01a5281f 57050184f3d962bf91511271af59ee20f3686c3f Sebastian Thiel <byronimo@gmail.com> 1277301014 +0200 merge fromtree: Merge made by recursive.
57050184f3d962bf91511271af59ee20f3686c3f 129f90aa8d83d9b250c87b0ba790605c4a2bb06a Sebastian Thiel <byronimo@gmail.com> 1277334478 +0200 commit: Multiple partly critical bugfixes related to index handling
129f90aa8d83d9b250c87b0ba790605c4a2bb06a a1adb421c2ee3e4868ea70d440dd82896219ed8f Sebastian Thiel <byronimo@gmail.com> 1277388148 +0200 commit: aggressive_tree_merge: fixed incorrect handling of one branch, it was just not implemented causing incorrect merge results. Added test to cover this issue
a1adb421c2ee3e4868ea70d440dd82896219ed8f 55dcc17c331f580b3beeb4d5decf64d3baf94f2e Sebastian Thiel <byronimo@gmail.com> 1277395720 +0200 commit (amend): aggressive_tree_merge: fixed incorrect handling of one branch, it was just not implemented causing incorrect merge results. Added test to cover this issue
55dcc17c331f580b3beeb4d5decf64d3baf94f2e ca131dd61e26f46f49ee3f70763f994cf9512665 Sebastian Thiel <byronimo@gmail.com> 1277401303 +0200 commit: GitCmdStreamReader: fixed terrible bug which only kicked in if the stream was actually empty. This is a rare case that can happen during stream testing. Theoretically there shouldn't be any empty streams of course, but practically they do exist sometimes ;)
ca131dd61e26f46f49ee3f70763f994cf9512665 feb1ea0f4aacb9ea6dc4133900e65bf34c0ee02d Sebastian Thiel <byronimo@gmail.com> 1277401306 +0200 commit (amend): GitCmdStreamReader: fixed terrible bug which only kicked in if the stream was actually empty. This is a rare case that can happen during stream testing. Theoretically there shouldn't be any empty streams of course, but practically they do exist sometimes ;); fixed stream.seek implementation, which previously used seek on standard output
feb1ea0f4aacb9ea6dc4133900e65bf34c0ee02d 402a6c2808db4333217aa300d0312836fd7923bd Sebastian Thiel <byronimo@gmail.com> 1277407147 +0200 commit: IndexFile.add: writing of the index file can now optionally be turned off. The default is to write the physical index, which is the behaviour you would expect
402a6c2808db4333217aa300d0312836fd7923bd 402a6c2808db4333217aa300d0312836fd7923bd Sebastian Thiel <byronimo@gmail.com> 1277417929 +0200 checkout: moving from master to index
402a6c2808db4333217aa300d0312836fd7923bd 4d30dfb07f78517b1ba20b88506e01678edd527c Sebastian Thiel <byronimo@gmail.com> 1277417979 +0200 commit: index.reset is now partly implemented using python, but in fact it resorts to using git-read-tree to keep the stat information when merging one tree in. After all this is what needed to be implemented in python as well
4d30dfb07f78517b1ba20b88506e01678edd527c 58fb1187b7b8f1e62d3930bdba9be5aba47a52c6 Sebastian Thiel <byronimo@gmail.com> 1277473186 +0200 commit (amend): index.reset is now partly implemented using python, but in fact it resorts to using git-read-tree to keep the stat information when merging one tree in. After all this is what needed to be implemented in python as well
58fb1187b7b8f1e62d3930bdba9be5aba47a52c6 402a6c2808db4333217aa300d0312836fd7923bd Sebastian Thiel <byronimo@gmail.com> 1277473192 +0200 checkout: moving from index to master
402a6c2808db4333217aa300d0312836fd7923bd 58fb1187b7b8f1e62d3930bdba9be5aba47a52c6 Sebastian Thiel <byronimo@gmail.com> 1277473196 +0200 merge index: Fast-forward
58fb1187b7b8f1e62d3930bdba9be5aba47a52c6 58fb1187b7b8f1e62d3930bdba9be5aba47a52c6 Sebastian Thiel <byronimo@gmail.com> 1277473218 +0200 checkout: moving from master to sha20
58fb1187b7b8f1e62d3930bdba9be5aba47a52c6 47e3138ee978ce708a41f38a0d874376d7ae5c78 Sebastian Thiel <byronimo@gmail.com> 1277503104 +0200 commit: Adjusted all files to (hopefully) deal with the fact that all objects now use 20 byte sha's internally as it is closer to the GitDB implementation
47e3138ee978ce708a41f38a0d874376d7ae5c78 7abe9065aab9dec56015ede5f2b0082837c5dc2e Sebastian Thiel <byronimo@gmail.com> 1277745342 +0200 commit: All tests adjusted to work with the changed internal sha representation
7abe9065aab9dec56015ede5f2b0082837c5dc2e 1fe889ea0cb2547584075dc1eb77f52c54b9a8c4 Sebastian Thiel <byronimo@gmail.com> 1277745354 +0200 commit (amend): All tests adjusted to work with the changed internal sha representation
1fe889ea0cb2547584075dc1eb77f52c54b9a8c4 58fb1187b7b8f1e62d3930bdba9be5aba47a52c6 Sebastian Thiel <byronimo@gmail.com> 1277745377 +0200 checkout: moving from sha20 to master
58fb1187b7b8f1e62d3930bdba9be5aba47a52c6 8d2239f24f6a54d98201413d4f46256df0d6a5f3 Sebastian Thiel <byronimo@gmail.com> 1277745383 +0200 merge sha20: Merge made by recursive.
8d2239f24f6a54d98201413d4f46256df0d6a5f3 f1401803ccf7db5d897a5ef4b27e2176627c430e Sebastian Thiel <byronimo@gmail.com> 1277756712 +0200 commit: Fixed performance tests which broke in the course of the sha1-20 byte changes
f1401803ccf7db5d897a5ef4b27e2176627c430e 6917ae4ce9eaa0f5ea91592988c1ea830626ac3a Sebastian Thiel <byronimo@gmail.com> 1277806256 +0200 commit: Diff: fixed bug that caused a string to end up as a blob mode
6917ae4ce9eaa0f5ea91592988c1ea830626ac3a 6917ae4ce9eaa0f5ea91592988c1ea830626ac3a Sebastian Thiel <byronimo@gmail.com> 1277819766 +0200 checkout: moving from master to docs
6917ae4ce9eaa0f5ea91592988c1ea830626ac3a 160081b9a7ca191afbec077c4bf970cfd9070d2c Sebastian Thiel <byronimo@gmail.com> 1277828911 +0200 commit: Updated and fixed sphinx API docs, which included one quick skim-through
160081b9a7ca191afbec077c4bf970cfd9070d2c 791765c0dc2d00a9ffa4bc857d09f615cfe3a759 Sebastian Thiel <byronimo@gmail.com> 1277830741 +0200 commit: Removed repo tests which for some reason left the 'repos' directory around, replaced them by a real test which actually executes code, and puts everything into the tmp directory
791765c0dc2d00a9ffa4bc857d09f615cfe3a759 77cd6659b64cb1950a82e6a3cccdda94f15ae739 Sebastian Thiel <byronimo@gmail.com> 1277834446 +0200 commit: Renamed modules utils to util, and errors to exc to be more conforming to the submodules's naming conventions
77cd6659b64cb1950a82e6a3cccdda94f15ae739 18be0972304dc7f1a2a509595de7da689bddbefa Sebastian Thiel <byronimo@gmail.com> 1277835397 +0200 commit: Removed blob.data property as there is no real reason for an exception to the rule of trying not to cache possibly heavy data. The data_stream method should be used instead
18be0972304dc7f1a2a509595de7da689bddbefa 0369384c8b79c44c5369f1b6c05046899f8886da Sebastian Thiel <byronimo@gmail.com> 1277840971 +0200 commit: revised tutorial to match the changed usage, added basic information about object databases
0369384c8b79c44c5369f1b6c05046899f8886da ee58d55133c571db6384acf916e4a1c3592be07b Sebastian Thiel <byronimo@gmail.com> 1277848046 +0200 commit: Added whatsnew and put it into the index
ee58d55133c571db6384acf916e4a1c3592be07b 77d083040248deeccb3ac1ad125eb2969b5cb370 Sebastian Thiel <byronimo@gmail.com> 1277848184 +0200 commit (amend): Added whatsnew and put it into the index
77d083040248deeccb3ac1ad125eb2969b5cb370 fde6522c40a346c8b1d588a2b8d4dd362ae1f58f Sebastian Thiel <byronimo@gmail.com> 1277848539 +0200 commit (amend): Added whatsnew and put it into the index
fde6522c40a346c8b1d588a2b8d4dd362ae1f58f 28a33ca17ac5e0816a3e24febb47ffcefa663980 Sebastian Thiel <byronimo@gmail.com> 1277991925 +0200 commit: Added further information about the required submodules, and how to install them. Incremeneted version to 0.3.0 beta1
28a33ca17ac5e0816a3e24febb47ffcefa663980 586aa65f772f69eb6d08224968c74dda1ca45612 Sebastian Thiel <byronimo@gmail.com> 1277993503 +0200 commit: Moved all source files into the source folder, separating the build from the source directory
586aa65f772f69eb6d08224968c74dda1ca45612 d2ebc6193f7205fd1686678a5707262cb1c59bb0 Sebastian Thiel <byronimo@gmail.com> 1277993585 +0200 commit (amend): Moved all source files into the source folder, separating the build from the source directory
d2ebc6193f7205fd1686678a5707262cb1c59bb0 1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af Sebastian Thiel <byronimo@gmail.com> 1277994361 +0200 checkout: moving from docs to hub/0.2
1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af 7bfca5efce8988e6070e7284bd409d1a731a3fbc Sebastian Thiel <byronimo@gmail.com> 1277994419 +0200 checkout: moving from 1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af to hub/0.1
7bfca5efce8988e6070e7284bd409d1a731a3fbc d2ebc6193f7205fd1686678a5707262cb1c59bb0 Sebastian Thiel <byronimo@gmail.com> 1277994469 +0200 checkout: moving from 7bfca5efce8988e6070e7284bd409d1a731a3fbc to docs
d2ebc6193f7205fd1686678a5707262cb1c59bb0 c3bd05b426a0e3dec8224244c3c9c0431d1ff130 Sebastian Thiel <byronimo@gmail.com> 1277994911 +0200 commit: Added doc-index, which helps to keep documentation of prior but still somewhat supported versions alive
c3bd05b426a0e3dec8224244c3c9c0431d1ff130 6917ae4ce9eaa0f5ea91592988c1ea830626ac3a Sebastian Thiel <byronimo@gmail.com> 1277999895 +0200 checkout: moving from docs to master
6917ae4ce9eaa0f5ea91592988c1ea830626ac3a fd96cceded27d1372bdc1a851448d2d8613f60f3 Sebastian Thiel <byronimo@gmail.com> 1277999899 +0200 merge docs: Merge made by recursive.
fd96cceded27d1372bdc1a851448d2d8613f60f3 1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af Sebastian Thiel <byronimo@gmail.com> 1278082024 +0200 checkout: moving from master to 0.2.0-beta1
1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af fd96cceded27d1372bdc1a851448d2d8613f60f3 Sebastian Thiel <byronimo@gmail.com> 1278082288 +0200 checkout: moving from 1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af to master
fd96cceded27d1372bdc1a851448d2d8613f60f3 f683c6623f73252645bb2819673046c9d397c567 Sebastian Thiel <byronimo@gmail.com> 1278082451 +0200 commit: Fixed broken 0.2 documentation, it didn't contain the API reference previously due to import errors and a somewhat inconsistent working tree that occurred when switching branches ...
f683c6623f73252645bb2819673046c9d397c567 a4287f65878000b42d11704692f9ea3734014b4c Sebastian Thiel <byronimo@gmail.com> 1278092317 +0200 commit: win32 compatability adjustments
a4287f65878000b42d11704692f9ea3734014b4c a4287f65878000b42d11704692f9ea3734014b4c Sebastian Thiel <byronimo@gmail.com> 1278315351 +0200 checkout: moving from master to revparse
a4287f65878000b42d11704692f9ea3734014b4c f963881e53a9f0a2746a11cb9cdfa82eb1f90d8c Sebastian Thiel <byronimo@gmail.com> 1278369330 +0200 commit: Initial version of the rev-parse routine, which doesn't work too bad, but its still rather slow and many tests are not yet implemented
f963881e53a9f0a2746a11cb9cdfa82eb1f90d8c 1c6d7830d9b87f47a0bfe82b3b5424a32e3164ad Sebastian Thiel <byronimo@gmail.com> 1278405962 +0200 commit: RevParse now generally works, but there are still some more specialized tests missing
1c6d7830d9b87f47a0bfe82b3b5424a32e3164ad a32a6bcd784fca9cb2b17365591c29d15c2f638e Sebastian Thiel <byronimo@gmail.com> 1278407809 +0200 commit: Refs now use object.new_from_sha where possible, preventing git-batch-check to be started up for sha resolution
a32a6bcd784fca9cb2b17365591c29d15c2f638e 355aa879cff8630c9eedaf151f90a229f2ba5135 Sebastian Thiel <byronimo@gmail.com> 1278410951 +0200 commit: Implemented main rev-parsing, including long hexshas, tags and refs. Short Shas still to be done
355aa879cff8630c9eedaf151f90a229f2ba5135 73959f3a2d4f224fbda03c8a8850f66f53d8cb3b Sebastian Thiel <byronimo@gmail.com> 1278418771 +0200 commit (amend): Implemented main rev-parsing, including long hexshas, tags and refs. Short Shas still to be done
73959f3a2d4f224fbda03c8a8850f66f53d8cb3b 9059525a75b91e6eb6a425f1edcc608739727168 Sebastian Thiel <byronimo@gmail.com> 1278440512 +0200 commit: Made repo.py a package to allow better localization of functions and utilities - the repo module got rather large
9059525a75b91e6eb6a425f1edcc608739727168 f068cdc5a1a13539c4a1d756ae950aab65f5348b Sebastian Thiel <byronimo@gmail.com> 1278497773 +0200 commit: Initially working implementation of short-sha parsing and interpretation, thanks to new gitdb functionality
f068cdc5a1a13539c4a1d756ae950aab65f5348b bc31651674648f026464fd4110858c4ffeac3c18 Sebastian Thiel <byronimo@gmail.com> 1278516647 +0200 commit: Adjusted previous object creators to use the rev_parse method directly. rev_parse could be adjusted not to return Objects anymore, providing better performance for those who just want a sha only. On the other hand, the method is high-level and should be convenient to use as well, its a starting point for more usually, hence its unlikely to call it in tight loops
bc31651674648f026464fd4110858c4ffeac3c18 8a73805d9b26b5a6c54f2e8d53f948df7db8b3d4 Sebastian Thiel <byronimo@gmail.com> 1278517362 +0200 commit: Added test for GitCmdObjectDB in order to verify the method is working as expected with different input
8a73805d9b26b5a6c54f2e8d53f948df7db8b3d4 01ab5b96e68657892695c99a93ef909165456689 Sebastian Thiel <byronimo@gmail.com> 1278517373 +0200 commit (amend): Added test for GitCmdObjectDB in order to verify the partial_to_complete_sha_hex is working as expected with different input ( it wasn't, of course ;) )
01ab5b96e68657892695c99a93ef909165456689 a4287f65878000b42d11704692f9ea3734014b4c Sebastian Thiel <byronimo@gmail.com> 1278517411 +0200 checkout: moving from revparse to master
a4287f65878000b42d11704692f9ea3734014b4c ca288d443f4fc9d790eecb6e1cdf82b6cdd8dc0d Sebastian Thiel <byronimo@gmail.com> 1278517416 +0200 merge revparse: Merge made by recursive.
ca288d443f4fc9d790eecb6e1cdf82b6cdd8dc0d a4287f65878000b42d11704692f9ea3734014b4c Sebastian Thiel <byronimo@gmail.com> 1278525786 +0200 checkout: moving from master to master~1
a4287f65878000b42d11704692f9ea3734014b4c ca288d443f4fc9d790eecb6e1cdf82b6cdd8dc0d Sebastian Thiel <byronimo@gmail.com> 1278525793 +0200 checkout: moving from a4287f65878000b42d11704692f9ea3734014b4c to master
ca288d443f4fc9d790eecb6e1cdf82b6cdd8dc0d 01ab5b96e68657892695c99a93ef909165456689 Sebastian Thiel <byronimo@gmail.com> 1278525803 +0200 checkout: moving from master to master^2
01ab5b96e68657892695c99a93ef909165456689 bc31651674648f026464fd4110858c4ffeac3c18 Sebastian Thiel <byronimo@gmail.com> 1278525815 +0200 checkout: moving from 01ab5b96e68657892695c99a93ef909165456689 to master^2~1
bc31651674648f026464fd4110858c4ffeac3c18 f068cdc5a1a13539c4a1d756ae950aab65f5348b Sebastian Thiel <byronimo@gmail.com> 1278525821 +0200 checkout: moving from bc31651674648f026464fd4110858c4ffeac3c18 to master^2~2
f068cdc5a1a13539c4a1d756ae950aab65f5348b 73959f3a2d4f224fbda03c8a8850f66f53d8cb3b Sebastian Thiel <byronimo@gmail.com> 1278525826 +0200 checkout: moving from f068cdc5a1a13539c4a1d756ae950aab65f5348b to master^2~4
73959f3a2d4f224fbda03c8a8850f66f53d8cb3b ca288d443f4fc9d790eecb6e1cdf82b6cdd8dc0d Sebastian Thiel <byronimo@gmail.com> 1278525829 +0200 checkout: moving from 73959f3a2d4f224fbda03c8a8850f66f53d8cb3b to master
ca288d443f4fc9d790eecb6e1cdf82b6cdd8dc0d 5fd6cc37fd07c25cb921b77b4f658b7e8fc132b3 Sebastian Thiel <byronimo@gmail.com> 1278536545 +0200 commit: Adjusted clone method to allow static classmethod clone ( using clone_from ) as well as the previous instance method clone to keep it compatible
5fd6cc37fd07c25cb921b77b4f658b7e8fc132b3 76af62b3c5a26638fcad9a3fe401fba566fb7037 Sebastian Thiel <byronimo@gmail.com> 1278538933 +0200 commit (amend): Adjusted clone method to allow static classmethod clone ( using clone_from ) as well as the previous instance method clone to keep it compatible
76af62b3c5a26638fcad9a3fe401fba566fb7037 b425301ad16f265157abdaf47f7af1c1ea879068 Sebastian Thiel <byronimo@gmail.com> 1278539147 +0200 commit (amend): Adjusted clone method to allow static classmethod clone ( using clone_from ) as well as the previous instance method clone to keep it compatible
b425301ad16f265157abdaf47f7af1c1ea879068 3288a244428751208394d8137437878277ceb71f Sebastian Thiel <byronimo@gmail.com> 1278582561 +0200 commit: setup.py: fixed requirement - its interesting to see that there are two different keywords for distutils and setuptools, the latter one doesn't read the ones of the first one, unfortunately
3288a244428751208394d8137437878277ceb71f 08457a7a6b6ad4f518fad0d5bca094a2b5b38fbe Sebastian Thiel <byronimo@gmail.com> 1278670718 +0200 commit: Added python 2.4 support: Repo will now use the original GitCmdObjectDB in python 2.4, as the pure python implementation cannot work without memory maps
08457a7a6b6ad4f518fad0d5bca094a2b5b38fbe 258403da9c2a087b10082d26466528fce3de38d4 Sebastian Thiel <byronimo@gmail.com> 1278671744 +0200 commit: bumped verison to 0.3.0 beta2
258403da9c2a087b10082d26466528fce3de38d4 55b67e8194b8b4d9e73e27feadbf9af6593e4600 Sebastian Thiel <byronimo@gmail.com> 1278927490 +0200 pull gitorious master: Fast-forward
55b67e8194b8b4d9e73e27feadbf9af6593e4600 bcd37b68533d0cceb7e73dd1ed1428fa09f7dc17 Sebastian Thiel <byronimo@gmail.com> 1279007300 +0200 commit: Fixed incorrect use of Blob.data in performance test
bcd37b68533d0cceb7e73dd1ed1428fa09f7dc17 24740f22c59c3bcafa7b2c1f2ec997e4e14f3615 Sebastian Thiel <byronimo@gmail.com> 1279110447 +0200 commit: Added performance test to compare inst.__class__() vs type(inst)() class. The first one is faster, although I would have expected the latter one to be faster
24740f22c59c3bcafa7b2c1f2ec997e4e14f3615 24740f22c59c3bcafa7b2c1f2ec997e4e14f3615 Sebastian Thiel <byronimo@gmail.com> 1279130572 +0200 checkout: moving from master to integration
24740f22c59c3bcafa7b2c1f2ec997e4e14f3615 2603363b435a695f9bf1bbbd5c1c59c353ec0450 Sebastian Thiel <byronimo@gmail.com> 1279130575 +0200 pull git://gitorious.org/git-python/mainline.git refs/merge-requests/2104: Merge made by recursive.
2603363b435a695f9bf1bbbd5c1c59c353ec0450 c9c8d48a42d45d22a120e2191ae2f838483caccc Sebastian Thiel <byronimo@gmail.com> 1279130613 +0200 HEAD^2: updating HEAD
c9c8d48a42d45d22a120e2191ae2f838483caccc 24740f22c59c3bcafa7b2c1f2ec997e4e14f3615 Sebastian Thiel <byronimo@gmail.com> 1279130629 +0200 checkout: moving from integration to master
24740f22c59c3bcafa7b2c1f2ec997e4e14f3615 a64c5f23c38f06c37a0ca64796368019dfb0d818 Sebastian Thiel <byronimo@gmail.com> 1279130658 +0200 commit: Correct Repo documentation in API reference
a64c5f23c38f06c37a0ca64796368019dfb0d818 24740f22c59c3bcafa7b2c1f2ec997e4e14f3615 Sebastian Thiel <byronimo@gmail.com> 1279130719 +0200 HEAD~1: updating HEAD
24740f22c59c3bcafa7b2c1f2ec997e4e14f3615 17a172920fde8c6688c8a1a39f258629b8b73757 Sebastian Thiel <byronimo@gmail.com> 1279131269 +0200 commit: Fixed the missing Repo type documentation thanks to a hint of gasche, fixed a few more resT syntax errors on the way
17a172920fde8c6688c8a1a39f258629b8b73757 cbb58869063fe803d232f099888fe9c23510de7b Sebastian Thiel <byronimo@gmail.com> 1279197254 +0200 merge gitorious/master: Fast-forward
cbb58869063fe803d232f099888fe9c23510de7b 89422841e46efa99bda49acfbe33ee1ca5122845 Sebastian Thiel <byronimo@gmail.com> 1279533542 +0200 pull gitorious master: Fast-forward
89422841e46efa99bda49acfbe33ee1ca5122845 192472f9673b18c91ce618e64e935f91769c50e7 Sebastian Thiel <byronimo@gmail.com> 1279814094 +0200 merge gitorious/master: Fast-forward
192472f9673b18c91ce618e64e935f91769c50e7 fa3fce80aeb57299633535b9e1c9d08db710ff05 Sebastian Thiel <byronimo@gmail.com> 1281701051 +0200 commit: unicode handling in messages and trees was improved. Messages are now written according to the encoding of the commit object, and decoded using that information as well. Trees will encode and decode their names with utf8
fa3fce80aeb57299633535b9e1c9d08db710ff05 394ed7006ee5dc8bddfd132b64001d5dfc0ffdd3 Sebastian Thiel <byronimo@gmail.com> 1281702078 +0200 commit (amend): unicode handling in messages and trees was improved. Messages are now written according to the encoding of the commit object, and decoded using that information as well. Trees will encode and decode their names with utf8
394ed7006ee5dc8bddfd132b64001d5dfc0ffdd3 394ed7006ee5dc8bddfd132b64001d5dfc0ffdd3 Sebastian Thiel <byronimo@gmail.com> 1283969667 +0200 checkout: moving from master to integration
394ed7006ee5dc8bddfd132b64001d5dfc0ffdd3 c4d5caa79e6d88bb3f98bfbefa3bfa039c7e157a Sebastian Thiel <byronimo@gmail.com> 1283969669 +0200 pull git://gitorious.org/git-python/mainline.git refs/merge-requests/14: Fast-forward
c4d5caa79e6d88bb3f98bfbefa3bfa039c7e157a 394ed7006ee5dc8bddfd132b64001d5dfc0ffdd3 Sebastian Thiel <byronimo@gmail.com> 1283969687 +0200 checkout: moving from integration to master
394ed7006ee5dc8bddfd132b64001d5dfc0ffdd3 c4d5caa79e6d88bb3f98bfbefa3bfa039c7e157a Sebastian Thiel <byronimo@gmail.com> 1283969691 +0200 merge integration: Fast-forward
c4d5caa79e6d88bb3f98bfbefa3bfa039c7e157a 741dfaadf732d4a2a897250c006d5ef3d3cd9f3a Sebastian Thiel <byronimo@gmail.com> 1287134990 +0200 commit: Fixed bug in http://byronimo.lighthouseapp.com/projects/51787/tickets/44-remoteref-fails-when-there-is-character-in-the-name using supplied patch ( which was manually applied ).
741dfaadf732d4a2a897250c006d5ef3d3cd9f3a fc650aa6869639548435ce2760d42c9cdd909d99 Sebastian Thiel <byronimo@gmail.com> 1287135891 +0200 commit: Added test to verify the actor class can handle unicode names correctly. This works because regex can handle unicode, and will return unicode instances instead of strings if required. Its quite amazing actually.
fc650aa6869639548435ce2760d42c9cdd909d99 741dfaadf732d4a2a897250c006d5ef3d3cd9f3a Sebastian Thiel <byronimo@gmail.com> 1287136504 +0200 HEAD~1: updating HEAD
741dfaadf732d4a2a897250c006d5ef3d3cd9f3a 741dfaadf732d4a2a897250c006d5ef3d3cd9f3a Sebastian Thiel <byronimo@gmail.com> 1287136515 +0200 checkout: moving from master to unicode
741dfaadf732d4a2a897250c006d5ef3d3cd9f3a 0f88fb96869b6ac3ed4dac7d23310a9327d3c89c Sebastian Thiel <byronimo@gmail.com> 1287136588 +0200 commit: Added test to verify the actor type can handle and parse unicode if it is passed in
0f88fb96869b6ac3ed4dac7d23310a9327d3c89c d39889bcac1735e429ac640ac6838d0e56835afb Sebastian Thiel <byronimo@gmail.com> 1287138883 +0200 commit: Added unicode handling for author names. They will now be properly encoded into the byte stream, as well as decoded from it
d39889bcac1735e429ac640ac6838d0e56835afb 741dfaadf732d4a2a897250c006d5ef3d3cd9f3a Sebastian Thiel <byronimo@gmail.com> 1287138889 +0200 checkout: moving from unicode to master
741dfaadf732d4a2a897250c006d5ef3d3cd9f3a a88173281ec56cb378a293d0170e11a1bda96a55 Sebastian Thiel <byronimo@gmail.com> 1287138898 +0200 merge unicode: Merge made by recursive.
a88173281ec56cb378a293d0170e11a1bda96a55 d39889bcac1735e429ac640ac6838d0e56835afb Sebastian Thiel <byronimo@gmail.com> 1287139063 +0200 checkout: moving from master to unicode
d39889bcac1735e429ac640ac6838d0e56835afb a88173281ec56cb378a293d0170e11a1bda96a55 Sebastian Thiel <byronimo@gmail.com> 1287139078 +0200 checkout: moving from unicode to master
a88173281ec56cb378a293d0170e11a1bda96a55 741dfaadf732d4a2a897250c006d5ef3d3cd9f3a Sebastian Thiel <byronimo@gmail.com> 1287139082 +0200 HEAD~1: updating HEAD
741dfaadf732d4a2a897250c006d5ef3d3cd9f3a d39889bcac1735e429ac640ac6838d0e56835afb Sebastian Thiel <byronimo@gmail.com> 1287139092 +0200 checkout: moving from master to unicode
d39889bcac1735e429ac640ac6838d0e56835afb 0019d7dc8c72839d238065473a62b137c3c350f5 Sebastian Thiel <byronimo@gmail.com> 1287139239 +0200 commit (amend): Added unicode handling for author names. They will now be properly encoded into the byte stream, as well as decoded from it
0019d7dc8c72839d238065473a62b137c3c350f5 741dfaadf732d4a2a897250c006d5ef3d3cd9f3a Sebastian Thiel <byronimo@gmail.com> 1287139252 +0200 checkout: moving from unicode to master
741dfaadf732d4a2a897250c006d5ef3d3cd9f3a 13647590f96fb5a22cb60f12c5a70e00065a7f3a Sebastian Thiel <byronimo@gmail.com> 1287139254 +0200 merge unicode: Merge made by recursive.
13647590f96fb5a22cb60f12c5a70e00065a7f3a 94029ce1420ced83c3e5dcd181a2280b26574bc9 Sebastian Thiel <byronimo@gmail.com> 1287139994 +0200 commit: Adjusted regex to support whitespace - it was a little restrictive previously, although there was absolutely no need for that.
94029ce1420ced83c3e5dcd181a2280b26574bc9 cfd2121eda5fadd18fba6819f90efb8868fad14a Sebastian Thiel <byronimo@gmail.com> 1287729183 +0200 checkout: moving from master to dotsenko/stable
cfd2121eda5fadd18fba6819f90efb8868fad14a 7bfca5efce8988e6070e7284bd409d1a731a3fbc Sebastian Thiel <byronimo@gmail.com> 1287729293 +0200 checkout: moving from cfd2121eda5fadd18fba6819f90efb8868fad14a to HEAD~1
7bfca5efce8988e6070e7284bd409d1a731a3fbc 94029ce1420ced83c3e5dcd181a2280b26574bc9 Sebastian Thiel <byronimo@gmail.com> 1287729331 +0200 checkout: moving from 7bfca5efce8988e6070e7284bd409d1a731a3fbc to master
94029ce1420ced83c3e5dcd181a2280b26574bc9 8858a63cb33319f3e739edcbfafdae3ec0fefa33 Sebastian Thiel <byronimo@gmail.com> 1288000691 +0200 commit: .gitignore will now ignore netbeans projects
8858a63cb33319f3e739edcbfafdae3ec0fefa33 a2b9ded87baf0f32ae94c10c5851a0468a45f003 Sebastian Thiel <byronimo@gmail.com> 1288198935 +0200 commit: docs: untracked_files is a property, but was used like a function, see http://groups.google.com/group/git-python/browse_thread/thread/84ed1835e26a5296?hl=en
a2b9ded87baf0f32ae94c10c5851a0468a45f003 1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af Sebastian Thiel <byronimo@gmail.com> 1288198941 +0200 checkout: moving from master to 0.2.0-beta1
1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af a2b9ded87baf0f32ae94c10c5851a0468a45f003 Sebastian Thiel <byronimo@gmail.com> 1288198964 +0200 checkout: moving from 1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af to master
a2b9ded87baf0f32ae94c10c5851a0468a45f003 8858a63cb33319f3e739edcbfafdae3ec0fefa33 Sebastian Thiel <byronimo@gmail.com> 1288198984 +0200 HEAD~1: updating HEAD
8858a63cb33319f3e739edcbfafdae3ec0fefa33 148eb761aeaa4c3913e1766db0a7df0a5b5c8b20 Sebastian Thiel <byronimo@gmail.com> 1288198991 +0200 commit: docs: untracked_files is a property, but was used like a function, see http://groups.google.com/group/git-python/browse_thread/thread/84ed1835e26a5296?hl=en
148eb761aeaa4c3913e1766db0a7df0a5b5c8b20 1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af Sebastian Thiel <byronimo@gmail.com> 1288198995 +0200 checkout: moving from master to 0.2.0-beta1
1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af 148eb761aeaa4c3913e1766db0a7df0a5b5c8b20 Sebastian Thiel <byronimo@gmail.com> 1288199017 +0200 checkout: moving from 1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af to master
148eb761aeaa4c3913e1766db0a7df0a5b5c8b20 8858a63cb33319f3e739edcbfafdae3ec0fefa33 Sebastian Thiel <byronimo@gmail.com> 1288199023 +0200 HEAD~1: updating HEAD
8858a63cb33319f3e739edcbfafdae3ec0fefa33 538e8265e04f69bb9bd73a10ddb4e8e9677fb140 Sebastian Thiel <byronimo@gmail.com> 1288199049 +0200 commit: docs: untracked_files is a property, but was used like a function, see http://groups.google.com/group/git-python/browse_thread/thread/84ed1835e26a5296?hl=en
538e8265e04f69bb9bd73a10ddb4e8e9677fb140 1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af Sebastian Thiel <byronimo@gmail.com> 1288199066 +0200 checkout: moving from master to 0.2.0-beta1
1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af ec97ed84d114ef131fa98acee4ce7be32f8c591f Sebastian Thiel <byronimo@gmail.com> 1288199163 +0200 commit: docs: untracked_files is a property, but was used like a function, see http://groups.google.com/group/git-python/browse_thread/thread/84ed1835e26a5296?hl=en
ec97ed84d114ef131fa98acee4ce7be32f8c591f ec97ed84d114ef131fa98acee4ce7be32f8c591f Sebastian Thiel <byronimo@gmail.com> 1288199260 +0200 checkout: moving from ec97ed84d114ef131fa98acee4ce7be32f8c591f to 0.2fixes
ec97ed84d114ef131fa98acee4ce7be32f8c591f 538e8265e04f69bb9bd73a10ddb4e8e9677fb140 Sebastian Thiel <byronimo@gmail.com> 1288199399 +0200 checkout: moving from 0.2 to master
538e8265e04f69bb9bd73a10ddb4e8e9677fb140 97ab197140b16027975c7465a5e8786e6cc8fea1 Sebastian Thiel <byronimo@gmail.com> 1288203452 +0200 commit (amend): docs: untracked_files is a property, but was used like a function, see http://groups.google.com/group/git-python/browse_thread/thread/84ed1835e26a5296?hl=en
97ab197140b16027975c7465a5e8786e6cc8fea1 3da3837fe2ec8152e1460f747d18290b52304868 Sebastian Thiel <byronimo@gmail.com> 1288203532 +0200 commit: cmd: improved error handling and debug printing
3da3837fe2ec8152e1460f747d18290b52304868 2c0b92e40ece170b59bced0cea752904823e06e7 Sebastian Thiel <byronimo@gmail.com> 1288203543 +0200 commit (amend): cmd: improved error handling and debug printing
2c0b92e40ece170b59bced0cea752904823e06e7 1b6b9510e0724bfcb4250f703ddf99d1e4020bbc Sebastian Thiel <byronimo@gmail.com> 1288205467 +0200 commit: Fixed bug that would cause the author's email to be a generic default one, instead of the existing and valid. The rest of the ConfigParser handling is correct, as it reads all configuration files available to git
1b6b9510e0724bfcb4250f703ddf99d1e4020bbc 0d5bfb5d6d22f8fe8c940f36e1fbe16738965d5f Sebastian Thiel <byronimo@gmail.com> 1288208986 +0200 commit: index.reset: updated parameter docs, but most importantly, the method now has better testing for the use of paths during reset. The IndexFile now implements this on its own, which also allows for something equivalent to git-reset --hard -- <paths>, which is not possible in the git command for some probably very good reason
0d5bfb5d6d22f8fe8c940f36e1fbe16738965d5f 702f3909520d39e8c343dece7b1e2d72e1479bbe Sebastian Thiel <byronimo@gmail.com> 1289032478 +0100 checkout: moving from master to integration
702f3909520d39e8c343dece7b1e2d72e1479bbe 0d5bfb5d6d22f8fe8c940f36e1fbe16738965d5f Sebastian Thiel <byronimo@gmail.com> 1289032798 +0100 checkout: moving from integration to master
0d5bfb5d6d22f8fe8c940f36e1fbe16738965d5f 735b28bc65964da5b181dffcccb1d05555b5acab Sebastian Thiel <byronimo@gmail.com> 1289033220 +0100 commit: test_refs: fixed failing tests just by making it less strict. It is dependent on the setup of the surrounding repository, hence the amount of ref-types found is actually variable, as long as they get more
735b28bc65964da5b181dffcccb1d05555b5acab 702f3909520d39e8c343dece7b1e2d72e1479bbe Sebastian Thiel <byronimo@gmail.com> 1289033228 +0100 checkout: moving from master to integration
702f3909520d39e8c343dece7b1e2d72e1479bbe b14a52ae2f8d6e38f57c34f8be150541cf00dafa Sebastian Thiel <byronimo@gmail.com> 1289034355 +0100 commit: Some cleanup of Daniels contribution. Now it appears to be ready for a merge
b14a52ae2f8d6e38f57c34f8be150541cf00dafa 90d73cd6ffa6e848da59cb2a35dec74e0fabd00a Sebastian Thiel <byronimo@gmail.com> 1289034615 +0100 commit (amend): Some cleanup of Daniels contribution. Now it appears to be ready for a merge
90d73cd6ffa6e848da59cb2a35dec74e0fabd00a 7bfca5efce8988e6070e7284bd409d1a731a3fbc Sebastian Thiel <byronimo@gmail.com> 1289034647 +0100 checkout: moving from integration to 0.1
7bfca5efce8988e6070e7284bd409d1a731a3fbc 90d73cd6ffa6e848da59cb2a35dec74e0fabd00a Sebastian Thiel <byronimo@gmail.com> 1289034656 +0100 merge integration: Fast-forward
90d73cd6ffa6e848da59cb2a35dec74e0fabd00a 735b28bc65964da5b181dffcccb1d05555b5acab Sebastian Thiel <byronimo@gmail.com> 1289034706 +0100 checkout: moving from 0.1 to master
735b28bc65964da5b181dffcccb1d05555b5acab bd7fb976ab0607592875b5697dc76c117a18dc73 Sebastian Thiel <byronimo@gmail.com> 1289034759 +0100 commit (amend): test_refs: fixed failing tests just by making it less strict. It is dependent on the setup of the surrounding repository, hence the amount of ref-types found is actually variable, as long as they get more
bd7fb976ab0607592875b5697dc76c117a18dc73 258403da9c2a087b10082d26466528fce3de38d4 Sebastian Thiel <byronimo@gmail.com> 1289338336 +0100 checkout: moving from master to 0.3.0-beta2
258403da9c2a087b10082d26466528fce3de38d4 bd7fb976ab0607592875b5697dc76c117a18dc73 Sebastian Thiel <byronimo@gmail.com> 1289338796 +0100 checkout: moving from 258403da9c2a087b10082d26466528fce3de38d4 to master
bd7fb976ab0607592875b5697dc76c117a18dc73 a1d1d2cb421f16bd277d7c4ce88398ff0f5afb29 Sebastian Thiel <byronimo@gmail.com> 1289379557 +0100 commit: tutorial: Fixed incorrect initialization code for bare repo, thank you, Bryan Bishop
a1d1d2cb421f16bd277d7c4ce88398ff0f5afb29 a1d1d2cb421f16bd277d7c4ce88398ff0f5afb29 Sebastian Thiel <byronimo@gmail.com> 1289385325 +0100 checkout: moving from master to submodule
a1d1d2cb421f16bd277d7c4ce88398ff0f5afb29 a1e2f63e64875a29e8c01a7ae17f5744680167a5 Sebastian Thiel <byronimo@gmail.com> 1289817434 +0100 commit: submodule: Fleshed out interface, and a partial test which is not yet usable. It showed that the ConfigParser needs some work. If the root is set, it also needs to refer to the root_commit instead of to the root-tree, as it will have to decide whether it works on the working tree's version of the .gitmodules file or the one in the repository
a1e2f63e64875a29e8c01a7ae17f5744680167a5 4d36f8ff4d1274a8815e932285ad6dbd6b2888af Sebastian Thiel <byronimo@gmail.com> 1289819639 +0100 commit: Improved GitConfigurationParser to better deal with streams and the corresponding locks. Submodule class now operates on parent_commits, the configuration is either streamed from the repository or written directly into a blob ( or file ) dependending on whether we have a working tree checkout or not which matches our parent_commit
4d36f8ff4d1274a8815e932285ad6dbd6b2888af 00ce31ad308ff4c7ef874d2fa64374f47980c85c Sebastian Thiel <byronimo@gmail.com> 1289836392 +0100 commit: Objects: Constructor now manually checks and sets the input arguments to the local cache - previously a procedural approach was used, which was less code, but slower too. Especially in case of CommitObjects unrolling the loop manually makes a difference.
00ce31ad308ff4c7ef874d2fa64374f47980c85c f97653aa06cf84bcf160be3786b6fce49ef52961 Sebastian Thiel <byronimo@gmail.com> 1289842964 +0100 commit: Repo: added submodule query and iteration methods similar to the ones provided for Remotes, including test
f97653aa06cf84bcf160be3786b6fce49ef52961 624556eae1c292a1dc283d9dca1557e28abe8ee3 Sebastian Thiel <byronimo@gmail.com> 1289844233 +0100 commit: Optimized test-decorators, by completely removing with_bare_rw_repo, which was mainly copy-paste from with_rw_repo, what a shame
624556eae1c292a1dc283d9dca1557e28abe8ee3 ceee7d7e0d98db12067744ac3cd0ab3a49602457 Sebastian Thiel <byronimo@gmail.com> 1289855525 +0100 commit: Added partial implementation of update, but realized that using refs in general may be contradicting if a tag is given there, as well as a commit sha of the submodule. Hence it should really be only a branch
ceee7d7e0d98db12067744ac3cd0ab3a49602457 d923bce2a8964541cf804428ccf3953ebbbdcf7d Sebastian Thiel <byronimo@gmail.com> 1289863093 +0100 commit: Submodule now only supports branches to be given as hint that will svn-external like behaviour. Implemented first version of update, which works for now, but probably needs to see more features
d923bce2a8964541cf804428ccf3953ebbbdcf7d d4fd7fca515ba9b088a7c811292f76f47d16cd7b Sebastian Thiel <byronimo@gmail.com> 1289863587 +0100 commit (amend): Submodule now only supports branches to be given as hint that will svn-external like behaviour. Implemented first version of update, which works for now, but probably needs to see more features
d4fd7fca515ba9b088a7c811292f76f47d16cd7b af5abca21b56fcf641ff916bd567680888c364aa Sebastian Thiel <byronimo@gmail.com> 1289896210 +0100 commit: Added a few utility methods and improved the test. Refs need an improvement though to allow easy configuration of branch-specific settings
af5abca21b56fcf641ff916bd567680888c364aa 38b81ad137e5f5486ce97a35702c84b9f869ccef Sebastian Thiel <byronimo@gmail.com> 1289901931 +0100 commit: remote: added methods to set and query the tracking branch status of normal heads, including test.
38b81ad137e5f5486ce97a35702c84b9f869ccef 9f73e8ba55f33394161b403bf7b8c2e0e05f47b0 Sebastian Thiel <byronimo@gmail.com> 1289901972 +0100 commit (amend): remote: added methods to set and query the tracking branch status of normal heads, including test.
9f73e8ba55f33394161b403bf7b8c2e0e05f47b0 3035781875f3004734ff5fe3be77f66b3cef299e Sebastian Thiel <byronimo@gmail.com> 1289903243 +0100 commit: Improved efficiency of the submodule.update process, improved test
3035781875f3004734ff5fe3be77f66b3cef299e 21b4db556619db2ef25f0e0d90fef7e38e6713e5 Sebastian Thiel <byronimo@gmail.com> 1289903575 +0100 commit (amend): Improved efficiency of the submodule.update process, improved test
21b4db556619db2ef25f0e0d90fef7e38e6713e5 78d2cd65b8b778f3b0cfef5268b0684314ca22ef Sebastian Thiel <byronimo@gmail.com> 1289905889 +0100 commit: implemented update to_last_revision option including test. Its now possible to update submodules such as svn-externals
78d2cd65b8b778f3b0cfef5268b0684314ca22ef c750f599a1b05ac855b55abc771729a704119833 Sebastian Thiel <byronimo@gmail.com> 1289924204 +0100 commit: Implemented deletion of submodules including proper tests
c750f599a1b05ac855b55abc771729a704119833 3d061a1a506b71234f783628ba54a7bdf79bbce9 Sebastian Thiel <byronimo@gmail.com> 1289924802 +0100 commit (amend): Implemented deletion of submodules including proper tests
3d061a1a506b71234f783628ba54a7bdf79bbce9 98e6edb546116cd98abdc3b37c6744e859bbde5c Sebastian Thiel <byronimo@gmail.com> 1289930487 +0100 commit: Initial implementation of submodule.add without any tests. These are to come next
98e6edb546116cd98abdc3b37c6744e859bbde5c 33964afb47ce3af8a32e6613b0834e5f94bdfe68 Sebastian Thiel <byronimo@gmail.com> 1289938053 +0100 commit: Added tests for all failure modes of submodule add ( except for one ), and fixed a few issues on the way
33964afb47ce3af8a32e6613b0834e5f94bdfe68 7b3ef45167e1c2f7d1b7507c13fcedd914f87da9 Sebastian Thiel <byronimo@gmail.com> 1289938869 +0100 commit: The submodule's branch is now a branch instance, not a plain string anymore
7b3ef45167e1c2f7d1b7507c13fcedd914f87da9 ef48ca5f54fe31536920ec4171596ff8468db5fe Sebastian Thiel <byronimo@gmail.com> 1289950137 +0100 commit: Added rest of submodule.add test code which should be pretty much 100% coverage for it
ef48ca5f54fe31536920ec4171596ff8468db5fe e84d05f4bbf7090a9802e9cd198d1c383974cb12 Sebastian Thiel <byronimo@gmail.com> 1289989025 +0100 commit: Repo: scetched out submodule_update
e84d05f4bbf7090a9802e9cd198d1c383974cb12 403c31fe3c7075652c892ecd3b6dc6d321bb1226 Sebastian Thiel <byronimo@gmail.com> 1290001921 +0100 commit: index: Sped up reading and writing of the index file by reducing the amount of attribute lookups considerably
403c31fe3c7075652c892ecd3b6dc6d321bb1226 b03933057df80ea9f860cc616eb7733f140f866e Sebastian Thiel <byronimo@gmail.com> 1290001937 +0100 commit (amend): index: Sped up reading and writing of the index file by reducing the amount of attribute lookups considerably
b03933057df80ea9f860cc616eb7733f140f866e a1e6234c27abf041e4c8cd1a799950e7cd9104f6 Sebastian Thiel <byronimo@gmail.com> 1290003888 +0100 commit: Inital implementation of Submodule.move including a very simple and to-be-improved test
a1e6234c27abf041e4c8cd1a799950e7cd9104f6 abda960de327e922fd9eaa429bef9e92918c8387 Sebastian Thiel <byronimo@gmail.com> 1290010524 +0100 commit: submodule: removed module_path method as it is implemented in the abspath property alrdeady
abda960de327e922fd9eaa429bef9e92918c8387 609a46a72764dc71104aa5d7b1ca5f53d4237a75 Sebastian Thiel <byronimo@gmail.com> 1290011090 +0100 commit (amend): submodule: removed module_path method as it is implemented in the abspath property alrdeady
609a46a72764dc71104aa5d7b1ca5f53d4237a75 6066f04d529b04e96295b37b5cceb2556414a472 Sebastian Thiel <byronimo@gmail.com> 1290025995 +0100 commit: repo: Added create_submodule method which fits into the tradition of offering a create_* method for most important entities.
6066f04d529b04e96295b37b5cceb2556414a472 609a46a72764dc71104aa5d7b1ca5f53d4237a75 Sebastian Thiel <byronimo@gmail.com> 1290026006 +0100 HEAD~1: updating HEAD
609a46a72764dc71104aa5d7b1ca5f53d4237a75 7cc4d748a132377ffe63534e9777d7541a3253c5 Sebastian Thiel <byronimo@gmail.com> 1290026013 +0100 commit: repo: Added create_submodule method which fits into the tradition of offering a create_* method for most important entities.
7cc4d748a132377ffe63534e9777d7541a3253c5 1687283c13caf7ff8d1959591541dff6a171ca1e Sebastian Thiel <byronimo@gmail.com> 1290029890 +0100 commit: RootModule.update: initial implementation of update method, which should be able to handle submodule removals, additions, path changes and branch changes. All this still needs to be tested though
1687283c13caf7ff8d1959591541dff6a171ca1e 9a0c4009edb35bb81d7e41d7d235675ccdfcffba Sebastian Thiel <byronimo@gmail.com> 1290068415 +0100 commit: commit: when creating a new commit and advancing the head, it will now write the ORIG_HEAD reference as well
9a0c4009edb35bb81d7e41d7d235675ccdfcffba 7a320abc52307b4d4010166bd899ac75024ec9a7 Sebastian Thiel <byronimo@gmail.com> 1290068612 +0100 commit (amend): commit: when creating a new commit and advancing the head, it will now write the ORIG_HEAD reference as well
7a320abc52307b4d4010166bd899ac75024ec9a7 1185ee2c9cda379bada7e08694f13dc124b27e93 Sebastian Thiel <byronimo@gmail.com> 1290073216 +0100 commit: ORIG_HEAD handling is now implemented in the ref-class itself, instead of being a special case of the commit method; includes tests
1185ee2c9cda379bada7e08694f13dc124b27e93 82849578e61a7dfb47fc76dcbe18b1e3b6a36951 Sebastian Thiel <byronimo@gmail.com> 1290073599 +0100 commit (amend): ORIG_HEAD handling is now implemented in the ref-class itself, instead of being a special case of the commit method; includes tests
82849578e61a7dfb47fc76dcbe18b1e3b6a36951 0c1834134ce177cdbd30a56994fcc4bf8f5be8b2 Sebastian Thiel <byronimo@gmail.com> 1290076876 +0100 commit: Added test-setup which can test all aspects of the (smart) update method
0c1834134ce177cdbd30a56994fcc4bf8f5be8b2 50095005bab7ffae14cb7d2ec8faeee7eed579ee Sebastian Thiel <byronimo@gmail.com> 1290096572 +0100 commit: first update test succeeds, so it verifies that existing repositories can be moved later if the configuration changed
50095005bab7ffae14cb7d2ec8faeee7eed579ee ca86a09651674d4bd2c22fd7f2f2ae6ca1c1d3d6 Sebastian Thiel <byronimo@gmail.com> 1290097988 +0100 commit (amend): first update test succeeds, so it verifies that existing repositories can be moved later if the configuration changed
ca86a09651674d4bd2c22fd7f2f2ae6ca1c1d3d6 bf2f7449beafcfb4578d08c90370d3953ff5f073 Sebastian Thiel <byronimo@gmail.com> 1290098006 +0100 commit (amend): first update test succeeds, so it verifies that existing repositories can be moved later if the configuration changed, and actually it also verifies that the url-change is handled correctly (as we changed the url from the default to the local path)
bf2f7449beafcfb4578d08c90370d3953ff5f073 c0990b2a6dd2e777b46c1685ddb985b3c0ef59a2 Sebastian Thiel <byronimo@gmail.com> 1290098151 +0100 commit (amend): first update test succeeds, so it verifies that existing repositories can be moved later if the configuration changed, and actually it also verifies that the url-change is handled correctly (as we changed the url from the default to the local path)
c0990b2a6dd2e777b46c1685ddb985b3c0ef59a2 cf5eaddde33e983bc7b496f458bdd49154f6f498 Sebastian Thiel <byronimo@gmail.com> 1290109461 +0100 commit: Updated tests and implementation to verify functionality for handling submodule removals, as well as url changes
cf5eaddde33e983bc7b496f458bdd49154f6f498 3f2d76ba8e6d004ff5849ed8c7c34f6a4ac2e1e3 Sebastian Thiel <byronimo@gmail.com> 1290112561 +0100 commit: Added test for branch changes - it appears to work well, at least as far as the restricted tests are concerned
3f2d76ba8e6d004ff5849ed8c7c34f6a4ac2e1e3 ebe8f644e751c1b2115301c1a961bef14d2cce89 Sebastian Thiel <byronimo@gmail.com> 1290114666 +0100 commit: Added test for the recursive code path.
ebe8f644e751c1b2115301c1a961bef14d2cce89 ebe8f644e751c1b2115301c1a961bef14d2cce89 Sebastian Thiel <byronimo@gmail.com> 1290114835 +0100 checkout: moving from submodule to refactor
ebe8f644e751c1b2115301c1a961bef14d2cce89 4c34d5c3f2a4ed7194276a026e0ec6437d339c67 Sebastian Thiel <byronimo@gmail.com> 1290116693 +0100 commit: Split big submodule file into smaller files. Tried to manually get imports right, but its not yet tested
4c34d5c3f2a4ed7194276a026e0ec6437d339c67 9519f186ce757cdba217f222c95c20033d00f91d Sebastian Thiel <byronimo@gmail.com> 1290118223 +0100 commit: Fixed all imports, refactoring appears to be complete
9519f186ce757cdba217f222c95c20033d00f91d ebe8f644e751c1b2115301c1a961bef14d2cce89 Sebastian Thiel <byronimo@gmail.com> 1290118281 +0100 checkout: moving from refactor to submodule
ebe8f644e751c1b2115301c1a961bef14d2cce89 7cf2d5fcf0a3db793678dd6ba9fc1c24d4eeb36a Sebastian Thiel <byronimo@gmail.com> 1290118286 +0100 merge refactor: Merge made by recursive.
7cf2d5fcf0a3db793678dd6ba9fc1c24d4eeb36a a1d1d2cb421f16bd277d7c4ce88398ff0f5afb29 Sebastian Thiel <byronimo@gmail.com> 1290118288 +0100 checkout: moving from submodule to master
a1d1d2cb421f16bd277d7c4ce88398ff0f5afb29 f1545bd9cd6953c5b39c488bf7fe179676060499 Sebastian Thiel <byronimo@gmail.com> 1290118292 +0100 merge submodule: Merge made by recursive.
f1545bd9cd6953c5b39c488bf7fe179676060499 f1545bd9cd6953c5b39c488bf7fe179676060499 Sebastian Thiel <byronimo@gmail.com> 1290119272 +0100 checkout: moving from master to docs
f1545bd9cd6953c5b39c488bf7fe179676060499 75c75fa136f6181f6ba2e52b8b85a98d3fe1718e Sebastian Thiel <byronimo@gmail.com> 1290120222 +0100 commit: Changed name/id of gitdb submodule to something that doesn't look like a path
75c75fa136f6181f6ba2e52b8b85a98d3fe1718e a25e1d4aa7e5898ab1224d0e5cc5ecfbe8ed8821 Sebastian Thiel <byronimo@gmail.com> 1290122817 +0100 commit: Updated tutorial with a brief introduction to submodules
a25e1d4aa7e5898ab1224d0e5cc5ecfbe8ed8821 a8014d2ec56fd684dc81478dee73ca7eda0ab8a7 Sebastian Thiel <byronimo@gmail.com> 1290122851 +0100 commit: Updated gitdb submodule, and added note about how the submodule package manages its dependencies
a8014d2ec56fd684dc81478dee73ca7eda0ab8a7 f1545bd9cd6953c5b39c488bf7fe179676060499 Sebastian Thiel <byronimo@gmail.com> 1290122855 +0100 checkout: moving from docs to master
f1545bd9cd6953c5b39c488bf7fe179676060499 45c0f285a6d9d9214f8167742d12af2855f527fb Sebastian Thiel <byronimo@gmail.com> 1290122860 +0100 merge docs: Merge made by recursive.
45c0f285a6d9d9214f8167742d12af2855f527fb 315c303214cef855499f0c7eda46b7ed82dceecb Sebastian Thiel <byronimo@gmail.com> 1290158850 +0100 commit: test_submodule: fixed failures that arose due to changes of the original submodule names. Also, a major bug was fixed that cased submodules to always being updated recursively when using the RootModule.update method
315c303214cef855499f0c7eda46b7ed82dceecb 7dd618655c96ff32b5c30e41a5406c512bcbb65f Sebastian Thiel <byronimo@gmail.com> 1290158895 +0100 commit (amend): test_submodule: fixed failures that arose due to changes of the original submodule names. Also, a major bug was fixed that cased submodules to always being updated recursively when using the RootModule.update method
7dd618655c96ff32b5c30e41a5406c512bcbb65f 2ab454f0ccf09773a4f51045329a69fd73559414 Sebastian Thiel <byronimo@gmail.com> 1290188727 +0100 commit: remote: parsing of fetch information now reacts to fatal errors. Previously it would just bump into an assertion
2ab454f0ccf09773a4f51045329a69fd73559414 b00ad00130389f5b00da9dbfd89c3e02319d2999 Sebastian Thiel <byronimo@gmail.com> 1290196658 +0100 commit: submodule: When adding an existing submodule, when retrieving the binsha, we will now consider not only the tree, but the index too
b00ad00130389f5b00da9dbfd89c3e02319d2999 8867348ca772cdce7434e76eed141f035b63e928 Sebastian Thiel <byronimo@gmail.com> 1290196804 +0100 commit: Bumped version number to 0.3.1
8867348ca772cdce7434e76eed141f035b63e928 8d0aa1ef19e2c3babee458bd4504820f415148e0 Sebastian Thiel <byronimo@gmail.com> 1290271885 +0100 commit: Fixed performance tests which broke in the meanwhile - they definitely don't run often enough, which is because they intentionally don't have a package initialization file
8d0aa1ef19e2c3babee458bd4504820f415148e0 8e0e315a371cdfc80993a1532f938d56ed7acee4 Sebastian Thiel <byronimo@gmail.com> 1290280591 +0100 commit: submodule: Fixed capital error when handling the submodule's branch, which was returned in the submodules super repository, not in the submodule's module
8e0e315a371cdfc80993a1532f938d56ed7acee4 7c72b9a3eaabbe927ba77d4f69a62f35fbe60e2e Sebastian Thiel <byronimo@gmail.com> 1290286993 +0100 merge gitorious/win32: Merge made by recursive.
7c72b9a3eaabbe927ba77d4f69a62f35fbe60e2e 7c72b9a3eaabbe927ba77d4f69a62f35fbe60e2e Sebastian Thiel <byronimo@gmail.com> 1290287857 +0100 checkout: moving from master to osx
7c72b9a3eaabbe927ba77d4f69a62f35fbe60e2e 6c6223b854e4c7985b08493a027b47f668493832 Sebastian Thiel <byronimo@gmail.com> 1290287908 +0100 commit: testing:added special case for osx to solve a special issue with the temp directory
6c6223b854e4c7985b08493a027b47f668493832 20b07fa542d2376a287435a26c967a5ee104667f Sebastian Thiel <byronimo@gmail.com> 1290288287 +0100 commit (amend): testing:added special case for osx to solve a special issue with the temp directory
20b07fa542d2376a287435a26c967a5ee104667f 7c72b9a3eaabbe927ba77d4f69a62f35fbe60e2e Sebastian Thiel <byronimo@gmail.com> 1290288327 +0100 checkout: moving from osx to master
7c72b9a3eaabbe927ba77d4f69a62f35fbe60e2e 517ae56f517f5e7253f878dd1dc3c7c49f53df1a Sebastian Thiel <byronimo@gmail.com> 1290288333 +0100 merge osx: Merge made by recursive.
517ae56f517f5e7253f878dd1dc3c7c49f53df1a 22a88a7ec38e29827264f558f0c1691b99102e23 Sebastian Thiel <byronimo@gmail.com> 1290289085 +0100 commit: fixed performance tests ... again, previously I was just working on an incorrect repository
22a88a7ec38e29827264f558f0c1691b99102e23 685760ab33b8f9d7455b18a9ecb8c4c5b3315d66 Sebastian Thiel <byronimo@gmail.com> 1290342054 +0100 commit: Added zip_safe info to setup.py file
685760ab33b8f9d7455b18a9ecb8c4c5b3315d66 9d6310db456de9952453361c860c3ae61b8674ea Sebastian Thiel <byronimo@gmail.com> 1290342681 +0100 commit: docs: added final docs for version 0.3.0, started new release 0.3.1
9d6310db456de9952453361c860c3ae61b8674ea 0b813371f5a8af95152cae109d28c7c97bfaf79f Sebastian Thiel <byronimo@gmail.com> 1290358083 +0100 commit: Fixed API reference docs as far as possible
0b813371f5a8af95152cae109d28c7c97bfaf79f 0b813371f5a8af95152cae109d28c7c97bfaf79f Sebastian Thiel <byronimo@gmail.com> 1290363019 +0100 checkout: moving from master to structure
0b813371f5a8af95152cae109d28c7c97bfaf79f fafe8a77db75083de3e7af92185ecdb7f2d542d3 Sebastian Thiel <byronimo@gmail.com> 1290363468 +0100 commit: moved all contents, incl. submodule gitdb, up to the root directory
fafe8a77db75083de3e7af92185ecdb7f2d542d3 e6019e16d5a74dc49eb7129ee7fd78b4de51dac2 Sebastian Thiel <byronimo@gmail.com> 1290363544 +0100 commit: flattened test folder structure, didn't adjust any file content yet
e6019e16d5a74dc49eb7129ee7fd78b4de51dac2 83a9e4a0dad595188ff3fb35bc3dfc4d931eff6d Sebastian Thiel <byronimo@gmail.com> 1290366107 +0100 commit: Fixed setup script to work with changed folder structure
83a9e4a0dad595188ff3fb35bc3dfc4d931eff6d 557800a1eba3b9cface0f319d8e6aa6bd2ec188d Sebastian Thiel <byronimo@gmail.com> 1290367011 +0100 commit: Updated MANIFEST and setup to include fixtures. Adjusted includes in all tests to work with the new directory structure
557800a1eba3b9cface0f319d8e6aa6bd2ec188d 9ca0f897376e765989e92e44628228514f431458 Sebastian Thiel <byronimo@gmail.com> 1290371673 +0100 commit (amend): Updated MANIFEST and setup to include fixtures. Adjusted includes in all tests to work with the new directory structure
9ca0f897376e765989e92e44628228514f431458 0b813371f5a8af95152cae109d28c7c97bfaf79f Sebastian Thiel <byronimo@gmail.com> 1290372294 +0100 checkout: moving from structure to master
0b813371f5a8af95152cae109d28c7c97bfaf79f 9ca0f897376e765989e92e44628228514f431458 Sebastian Thiel <byronimo@gmail.com> 1290372302 +0100 checkout: moving from master to structure
9ca0f897376e765989e92e44628228514f431458 6befb28efd86556e45bb0b213bcfbfa866cac379 Sebastian Thiel <byronimo@gmail.com> 1290372430 +0100 commit: updated changelog
6befb28efd86556e45bb0b213bcfbfa866cac379 0b813371f5a8af95152cae109d28c7c97bfaf79f Sebastian Thiel <byronimo@gmail.com> 1290372432 +0100 checkout: moving from structure to master
0b813371f5a8af95152cae109d28c7c97bfaf79f 94140bbfc523ae13e1e8045ebfed8a76fe0a1872 Sebastian Thiel <byronimo@gmail.com> 1290372438 +0100 merge structure: Merge made by recursive.
94140bbfc523ae13e1e8045ebfed8a76fe0a1872 d01b428dbac4103b4f7d7b8fca32e01f70746c53 Sebastian Thiel <byronimo@gmail.com> 1290372442 +0100 commit (amend): !!WARNING!!: Directory structure changed, see commit message for instructions
d01b428dbac4103b4f7d7b8fca32e01f70746c53 db3423d1eab11d00c5475e36eae8952512b07f4e Sebastian Thiel <byronimo@gmail.com> 1290373147 +0100 commit (amend): !**WARNING**!: Directory structure changed, see commit message for instructions
db3423d1eab11d00c5475e36eae8952512b07f4e 5ed5b2011ec7cf72f19e6d53b588eea4adca68e5 Sebastian Thiel <byronimo@gmail.com> 1290373168 +0100 commit (amend): *!*WARNING*!*: Directory structure changed, see commit message for instructions
5ed5b2011ec7cf72f19e6d53b588eea4adca68e5 470d4a7cc865d2702c326d9d1d1b0ab7afb49f0e Sebastian Thiel <byronimo@gmail.com> 1290373186 +0100 commit (amend): !##WARNING##!: Directory structure changed, see commit message for instructions
470d4a7cc865d2702c326d9d1d1b0ab7afb49f0e e088424eb01bd47c6f0d313f465a21ee742e6f4a Sebastian Thiel <byronimo@gmail.com> 1290373209 +0100 commit (amend): If you use git-python as a submodule of your own project, which alters the sys.path to import it,
e088424eb01bd47c6f0d313f465a21ee742e6f4a 48a17c87c15b2fa7ce2e84afa09484f354d57a39 Sebastian Thiel <byronimo@gmail.com> 1290373245 +0100 commit (amend): -#######->WARNING<-####### Directory structure changed, see commit message
48a17c87c15b2fa7ce2e84afa09484f354d57a39 fca367548e365f93c58c47dea45507025269f59a Sebastian Thiel <byronimo@gmail.com> 1290374761 +0100 commit: Changed version to 0.3.1 (removed beta1) so that other projects can actually depend on git-python using the setuptools. Previously it would claim the version did not exist, probably because the setuptools are just comparing strings
fca367548e365f93c58c47dea45507025269f59a fca367548e365f93c58c47dea45507025269f59a Sebastian Thiel <byronimo@gmail.com> 1290435685 +0100 checkout: moving from master to refs
fca367548e365f93c58c47dea45507025269f59a dec4663129f72321a14efd6de63f14a7419e3ed2 Sebastian Thiel <byronimo@gmail.com> 1290500057 +0100 commit: Split ref implementation up into multiple files, to make room for the log implementation
dec4663129f72321a14efd6de63f14a7419e3ed2 fca367548e365f93c58c47dea45507025269f59a Sebastian Thiel <byronimo@gmail.com> 1290500094 +0100 checkout: moving from refs to master
fca367548e365f93c58c47dea45507025269f59a dec4663129f72321a14efd6de63f14a7419e3ed2 Sebastian Thiel <byronimo@gmail.com> 1290500104 +0100 checkout: moving from master to refs
dec4663129f72321a14efd6de63f14a7419e3ed2 739fa140235cc9d65c632eaf1f5cacc944d87cfb Sebastian Thiel <byronimo@gmail.com> 1290501284 +0100 commit: Fixed remaining tests - lets hope that everything is indeed working correctly - as imports changed, every line of code needs to be run to assure all names can be resolved
739fa140235cc9d65c632eaf1f5cacc944d87cfb d89b2cbcd57ecd5600ecf0202b396141c1a856a3 Sebastian Thiel <byronimo@gmail.com> 1290512134 +0100 commit: Initial interface including some of the implementation of the RefLog. TestCase scetched out for now
d89b2cbcd57ecd5600ecf0202b396141c1a856a3 6e5aae2fc8c3832bdae1cd5e0a269405fb059231 Sebastian Thiel <byronimo@gmail.com> 1290512159 +0100 commit (amend): Initial interface including some of the implementation of the RefLog. TestCase scetched out for now
@@ -0,0 +1,2 @@
501bf602abea7d21c3dbb409b435976e92033145 82b8902e033430000481eb355733cd7065342037 Sebastian Thiel <byronimo@gmail.com> 1270634931 +0200 commit: Used this release for a first beta of the 0.2 branch of development
82b8902e033430000481eb355733cd7065342037 69361d96a59381fde0ac34d19df2d4aff05fb9a9 Sebastian Thiel <byronimo@gmail.com> 1271229940 commit: conf.py: Adjusted version to match with the actual version
@@ -0,0 +1,2 @@
501bf602abea7d21c3dbb409b435976e92033145 82b8902e033430000481eb355733cd7065342037 Sebastian Thiel <byronimo@gmail.com> 1270634931 +0200 commit: Used this release for a first beta of the 0.2 branch of development
82b8902e033430000481eb355733cd7065342037 69361d96a59381fde0ac34d19df2d4aff05fb9a9 Sebastian Thiel <byronimo@gmail. 1271229940 +0200 commit: conf.py: Adjusted version to match with the actual version
@@ -0,0 +1,2 @@
501bf602abea7d21c3dbb409b435976e92033145 82b8902e033430000481eb355733cd7065342037 Sebastian Thiel <byronimo@gmail.com> 1270634931 +0200 commit: Used this release for a first beta of the 0.2 branch of development
82b8902e033430000481eb355733cd7065342037 69361d96a59381fde0ac34d19df2d Sebastian Thiel <byronimo@gmail.com> 1271229940 +0200 commit: conf.py: Adjusted version to match with the actual version
@@ -0,0 +1,2 @@
501bf602abea7d21c3dbb409b435976e92033145 82b8902e033430000481eb355733cd7065342037 Sebastian Thiel <byronimo@gmail.com> 1270634931 +0200 commit: Used this release for a first beta of the 0.2 branch of development
82b8902e033430000481eb3 69361d96a59381fde0ac34d19df2d4aff05fb9a9 Sebastian Thiel <byronimo@gmail.com> 1271229940 +0200 commit: conf.py: Adjusted version to match with the actual version
@@ -0,0 +1,2 @@
501bf602abea7d21c3dbb409b435976e92033145 82b8902e033430000481eb355733cd7065342037 Sebastian Thiel <byronimo@gmail.com> 1270634931 +0200 commit: Used this release for a first beta of the 0.2 branch of development
82b8902e033430000481eb355733cd7065342037 69361d96a59381fde0ac34d19df2d4aff05fb9a9 Sebastian Thiel <byronimo@gmail.com> 1271229940 +0200 commit: conf.py: Adjusted version to match with the actual version
@@ -0,0 +1,124 @@
501bf602abea7d21c3dbb409b435976e92033145 82b8902e033430000481eb355733cd7065342037 Sebastian Thiel <byronimo@gmail.com> 1270634931 +0200 commit: Used this release for a first beta of the 0.2 branch of development
82b8902e033430000481eb355733cd7065342037 69361d96a59381fde0ac34d19df2d4aff05fb9a9 Sebastian Thiel <byronimo@gmail.com> 1271229940 +0200 commit: conf.py: Adjusted version to match with the actual version
69361d96a59381fde0ac34d19df2d4aff05fb9a9 0d6ceabf5b90e7c0690360fc30774d36644f563c Sebastian Thiel <byronimo@gmail.com> 1272614247 +0200 merge integration: Fast-forward
22a0289972b365b7912340501b52ca3dd98be289 143b927307d46ccb8f1cc095739e9625c03c82ff Sebastian Thiel <byronimo@gmail.com> 1272988814 +0200 commit: TODO: Removed all entries but left a mesage about where to find the issuee on lighthouse.
143b927307d46ccb8f1cc095739e9625c03c82ff e41c727be8dbf8f663e67624b109d9f8b135a4ab Sebastian Thiel <byronimo@gmail.com> 1273140152 +0200 commit: README: Added mailing list and issue tracker information
c083f3d0b853e723d0d4b00ff2f1ec5f65f05cba de5bc8f7076c5736ef1efa57345564fbc563bd19 Sebastian Thiel <byronimo@gmail.com> 1273522570 +0200 commit: Handle filenames with embedded spaces when generating diffs
de5bc8f7076c5736ef1efa57345564fbc563bd19 8caeec1b15645fa53ec5ddc6e990e7030ffb7c5a Sebastian Thiel <byronimo@gmail.com> 1273529174 +0200 commit: IndexFile.add: Fixed incorrect path handling if path rewriting was desired and absolute paths were given
600fcbc1a2d723f8d51e5f5ab6d9e4c389010e1c 1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af Sebastian Thiel <byronimo@gmail.com> 1274811103 +0200 commit: diff: by limiting the splitcount to 5, a subtle bug was introduced as the newline at the end of the split line was not split away automatically. Added test for this, and the trivial fix
1019d4cf68d1acdbb4d6c1abb7e71ac9c0f581af 17af1f64d5f1e62d40e11b75b1dd48e843748b49 Sebastian Thiel <byronimo@gmail.com> 1274877948 +0200 commit: BlockingLockFile: added sanity check that raises IOError if the directory containing the lock was removed. This is unlikely to happen in a production envrironment, but may happen during testing, as folders are moved/deleted once the test is complete. Daemons might still be waiting for something, and they should be allowed to terminate instead of waiting for a possibly long time
17af1f64d5f1e62d40e11b75b1dd48e843748b49 34ba8ffba0b3b4d21da7bcea594cc3631e422142 Sebastian Thiel <byronimo@gmail.com> 1274906080 +0200 commit: refs: a Reference can now be created by assigning a commit or object (for convenience)
34ba8ffba0b3b4d21da7bcea594cc3631e422142 11dc82538cc1ebb537c866c8e76146e384cdfe24 Sebastian Thiel <byronimo@gmail.com> 1274906333 +0200 commit: refs: a Reference can now be created by assigning a commit or object (for convenience)
11dc82538cc1ebb537c866c8e76146e384cdfe24 34ba8ffba0b3b4d21da7bcea594cc3631e422142 Sebastian Thiel <byronimo@gmail.com> 1274906338 +0200 HEAD~1: updating HEAD
34ba8ffba0b3b4d21da7bcea594cc3631e422142 de84cbdd0f9ef97fcd3477b31b040c57192e28d9 Sebastian Thiel <byronimo@gmail.com> 1274906431 +0200 commit (amend): refs: a Reference can now be created by assigning a commit or object (for convenience)
de84cbdd0f9ef97fcd3477b31b040c57192e28d9 ecf37a1b4c2f70f1fc62a6852f40178bf08b9859 Sebastian Thiel <byronimo@gmail.com> 1274910053 +0200 commit: index: index-add fixed to always append a newline after each item. In git has unified its way it reads from stdin, now it wants all items to be terminated by a newline usually. Previously, it could have been that it really didn't want to have a termination character when the last item was written to the file. Bumped the minimum requirements to 1.7.0 to be sure it is working as I think it will.
ecf37a1b4c2f70f1fc62a6852f40178bf08b9859 1ee2afb00afaf77c883501eac8cd614c8229a444 Sebastian Thiel <byronimo@gmail.com> 1274914700 +0200 commit: cmd: By default, on linux, the parent file handles will be closed to leave the child less cluttered, and make it easier to debug as it will only have the file descriptors we set. It appears to be more stable regarding the stdin-is-closed-but-child-doesn't-realize-this issue
1ee2afb00afaf77c883501eac8cd614c8229a444 bd45e9267ab0d3f37e59ecc8b87d0ad19abad4ad Sebastian Thiel <byronimo@gmail.com> 1275324366 +0200 commit: gitcmd: may now receive extra keyword arguments to be passed directly to the subproces.Popen invocation. It could be used to pass custom environments, without changing the own one
bd45e9267ab0d3f37e59ecc8b87d0ad19abad4ad 6d9b1f4f9fa8c9f030e3207e7deacc5d5f8bba4e Sebastian Thiel <byronimo@gmail.com> 1275324409 +0200 commit (amend): gitcmd: may now receive extra keyword arguments to be passed directly to the subproces.Popen invocation. It could be used to pass custom environments, without changing the own one (#26)
6d9b1f4f9fa8c9f030e3207e7deacc5d5f8bba4e e79999c956e2260c37449139080d351db4aa3627 Sebastian Thiel <byronimo@gmail.com> 1275549608 +0200 commit: git.cmd: moved hardcoded chunksize when duplicating stream data into easy-to-change class member variable
e79999c956e2260c37449139080d351db4aa3627 412632599479a8e5991a07ecb67bc52b85c60755 Sebastian Thiel <byronimo@gmail.com> 1275550524 +0200 commit: git.cmd: using communicate in the main branch of execution, which might not make a big difference, but perhaps its smarter about broken pipes.
412632599479a8e5991a07ecb67bc52b85c60755 25dca42bac17d511b7e2ebdd9d1d679e7626db5f Sebastian Thiel <byronimo@gmail.com> 1275550670 +0200 commit (amend): git.cmd: using communicate in the main branch of execution, which might not make a big difference, but perhaps its smarter about broken pipes.
25dca42bac17d511b7e2ebdd9d1d679e7626db5f 6fbb69306c0e14bacb8dcb92a89af27d3d5d631f Sebastian Thiel <byronimo@gmail.com> 1275665431 +0200 commit (merge): Merge branch 'odb'
6fbb69306c0e14bacb8dcb92a89af27d3d5d631f a243827ab3346e188e99db2f9fc1f916941c9b1a Sebastian Thiel <byronimo@gmail.com> 1275685591 +0200 commit: Implemented stream tests, found a bug on the way, slowly a test-framework for streams starts to show up, but its not yet there
a243827ab3346e188e99db2f9fc1f916941c9b1a 7c1169f6ea406fec1e26e99821e18e66437e65eb Sebastian Thiel <byronimo@gmail.com> 1275690001 +0200 commit: Removed compression flag from IStream and OStream types, as a valid object will always be compressed if generated by the system ( even future memory db's will compress it )
7c1169f6ea406fec1e26e99821e18e66437e65eb c69b6b979e3d6bd01ec40e75b92b21f7a391f0ca Sebastian Thiel <byronimo@gmail.com> 1275746174 +0200 commit: Added basic channel implementation including test
c69b6b979e3d6bd01ec40e75b92b21f7a391f0ca 7c1169f6ea406fec1e26e99821e18e66437e65eb Sebastian Thiel <byronimo@gmail.com> 1275746196 +0200 HEAD~1: updating HEAD
7c1169f6ea406fec1e26e99821e18e66437e65eb f91495e271597034226f1b9651345091083172c4 Sebastian Thiel <byronimo@gmail.com> 1276339280 +0200 merge async: Merge made by recursive.
f91495e271597034226f1b9651345091083172c4 5c631ca192848fed3068b31b1389cd92a0c0cdca Sebastian Thiel <byronimo@gmail.com> 1276340638 +0200 commit: Removed async from this repository, put it into own one which now comes in as external, using a git-submodule
5c631ca192848fed3068b31b1389cd92a0c0cdca f91495e271597034226f1b9651345091083172c4 Sebastian Thiel <byronimo@gmail.com> 1276345979 +0200 HEAD~1: updating HEAD
f91495e271597034226f1b9651345091083172c4 86ea63504f3e8a74cfb1d533be9d9602d2d17e27 Sebastian Thiel <byronimo@gmail.com> 1276346049 +0200 commit: Removed async from tree
86ea63504f3e8a74cfb1d533be9d9602d2d17e27 6c1faef799095f3990e9970bc2cb10aa0221cf9c Sebastian Thiel <byronimo@gmail.com> 1276356043 +0200 commit: Removed odb from project, it is now used as a submodule named gitdb, which was added instead
6c1faef799095f3990e9970bc2cb10aa0221cf9c 28ed48c93f4cc8b6dd23c951363e5bd4e6880992 Sebastian Thiel <byronimo@gmail.com> 1276503381 +0200 commit: Implemented initial version of tree serialization which appears to work according to a simple test
28ed48c93f4cc8b6dd23c951363e5bd4e6880992 fe5289ed8311fecf39913ce3ae86b1011eafe5f7 Sebastian Thiel <byronimo@gmail.com> 1276506168 +0200 commit: tree now uses less memory for its cache as it stores the bare deserialized information - this also speeds up later serialization after changes. its clear though that retrieving actual objects is slower currently as these are not cached anymore. Its worth thinking about moving these encoding, decoding routines to gitdb
fe5289ed8311fecf39913ce3ae86b1011eafe5f7 f8dabbf4f92a7023181777e9d40355562474f71a Sebastian Thiel <byronimo@gmail.com> 1276512508 +0200 commit: tree: added TreeModifier, allowing to adjust existing trees safely and or fast, while staying compatible with serialization which requires it to be sorted
f8dabbf4f92a7023181777e9d40355562474f71a d9240918aa03e49feabe43af619019805ac76786 Sebastian Thiel <byronimo@gmail.com> 1276512707 +0200 commit (amend): tree: added TreeModifier, allowing to adjust existing trees safely and or fast, while staying compatible with serialization which requires it to be sorted
d9240918aa03e49feabe43af619019805ac76786 38b3cfb9b24a108e0720f7a3f8d6355f7e0bb1a9 Sebastian Thiel <byronimo@gmail.com> 1276527612 +0200 merge index: Merge made by recursive.
38b3cfb9b24a108e0720f7a3f8d6355f7e0bb1a9 c9dbf201b4f0b3c2b299464618cb4ecb624d272c Sebastian Thiel <byronimo@gmail.com> 1276529105 +0200 commit: Moved small types that had their own module into the utils module
c9dbf201b4f0b3c2b299464618cb4ecb624d272c 45e87305bd4f050c2d0309c32fe5de499fc38df3 Sebastian Thiel <byronimo@gmail.com> 1276554725 +0200 commit: Reimplemented Lock handling to be conforming to the git lock protocol, which is actually more efficient than the previous implementation
45e87305bd4f050c2d0309c32fe5de499fc38df3 06590aee389f4466e02407f39af1674366a74705 Sebastian Thiel <byronimo@gmail.com> 1276555536 +0200 commit (amend): Reimplemented Lock handling to be conforming to the git lock protocol, which is actually more efficient than the previous implementation
06590aee389f4466e02407f39af1674366a74705 1d2307532d679393ae067326e4b6fa1a2ba5cc06 Sebastian Thiel <byronimo@gmail.com> 1276556905 +0200 commit: Moved LockedFD and its test into the gitdb project
1d2307532d679393ae067326e4b6fa1a2ba5cc06 e837b901dcfac82e864f806c80f4a9cbfdb9c9f3 Sebastian Thiel <byronimo@gmail.com> 1276607908 +0200 commit: Move LazyMixin type to gitdb, index reading now uses file_contents_ro from gitdb as well
e837b901dcfac82e864f806c80f4a9cbfdb9c9f3 b82dbf538ac0d03968a0f5b7e2318891abefafaa Sebastian Thiel <byronimo@gmail.com> 1276870827 +0200 commit: GitCmd implementation of gitdb base moved to git-python where it belongs. Previously it was located in gitdb, which doesn't have any facilities to use the git command
b82dbf538ac0d03968a0f5b7e2318891abefafaa f164627a85ed7b816759871a76db258515b85678 Sebastian Thiel <byronimo@gmail.com> 1277057845 +0200 commit: db: added pure python git database
f164627a85ed7b816759871a76db258515b85678 ac62760c52abf28d1fd863f0c0dd48bc4a23d223 Sebastian Thiel <byronimo@gmail.com> 1277117506 +0200 commit: index.add: now uses gitdb.store functionality instead of git-hash-file. The python version is about as fast, but could support multithreading using async
ac62760c52abf28d1fd863f0c0dd48bc4a23d223 0fdf6c3aaff49494c47aaeb0caa04b3016e10a26 Sebastian Thiel <byronimo@gmail.com> 1277127929 +0200 commit: index: Entries are now using flags internally, instead of reducing the flag information to just the stage ( just to be closer to the git-original )
0fdf6c3aaff49494c47aaeb0caa04b3016e10a26 0aeb491d3d8f53e07fb21f36251be4880170c5ab Sebastian Thiel <byronimo@gmail.com> 1277129321 +0200 commit: index.add does not need the git clt anymore
0aeb491d3d8f53e07fb21f36251be4880170c5ab 91725f0fc59aa05ef68ab96e9b29009ce84668a5 Sebastian Thiel <byronimo@gmail.com> 1277129385 +0200 commit (amend): index.add does not need the git clt anymore
91725f0fc59aa05ef68ab96e9b29009ce84668a5 778234d544b3f58dd415aaf10679d15b01a5281f Sebastian Thiel <byronimo@gmail.com> 1277201033 +0200 merge writetree: Merge made by recursive.
778234d544b3f58dd415aaf10679d15b01a5281f 57050184f3d962bf91511271af59ee20f3686c3f Sebastian Thiel <byronimo@gmail.com> 1277301014 +0200 merge fromtree: Merge made by recursive.
57050184f3d962bf91511271af59ee20f3686c3f 129f90aa8d83d9b250c87b0ba790605c4a2bb06a Sebastian Thiel <byronimo@gmail.com> 1277334478 +0200 commit: Multiple partly critical bugfixes related to index handling
129f90aa8d83d9b250c87b0ba790605c4a2bb06a a1adb421c2ee3e4868ea70d440dd82896219ed8f Sebastian Thiel <byronimo@gmail.com> 1277388148 +0200 commit: aggressive_tree_merge: fixed incorrect handling of one branch, it was just not implemented causing incorrect merge results. Added test to cover this issue
a1adb421c2ee3e4868ea70d440dd82896219ed8f 55dcc17c331f580b3beeb4d5decf64d3baf94f2e Sebastian Thiel <byronimo@gmail.com> 1277395720 +0200 commit (amend): aggressive_tree_merge: fixed incorrect handling of one branch, it was just not implemented causing incorrect merge results. Added test to cover this issue
55dcc17c331f580b3beeb4d5decf64d3baf94f2e ca131dd61e26f46f49ee3f70763f994cf9512665 Sebastian Thiel <byronimo@gmail.com> 1277401303 +0200 commit: GitCmdStreamReader: fixed terrible bug which only kicked in if the stream was actually empty. This is a rare case that can happen during stream testing. Theoretically there shouldn't be any empty streams of course, but practically they do exist sometimes ;)
ca131dd61e26f46f49ee3f70763f994cf9512665 feb1ea0f4aacb9ea6dc4133900e65bf34c0ee02d Sebastian Thiel <byronimo@gmail.com> 1277401306 +0200 commit (amend): GitCmdStreamReader: fixed terrible bug which only kicked in if the stream was actually empty. This is a rare case that can happen during stream testing. Theoretically there shouldn't be any empty streams of course, but practically they do exist sometimes ;); fixed stream.seek implementation, which previously used seek on standard output
feb1ea0f4aacb9ea6dc4133900e65bf34c0ee02d 402a6c2808db4333217aa300d0312836fd7923bd Sebastian Thiel <byronimo@gmail.com> 1277407147 +0200 commit: IndexFile.add: writing of the index file can now optionally be turned off. The default is to write the physical index, which is the behaviour you would expect
402a6c2808db4333217aa300d0312836fd7923bd 58fb1187b7b8f1e62d3930bdba9be5aba47a52c6 Sebastian Thiel <byronimo@gmail.com> 1277473196 +0200 merge index: Fast-forward
58fb1187b7b8f1e62d3930bdba9be5aba47a52c6 8d2239f24f6a54d98201413d4f46256df0d6a5f3 Sebastian Thiel <byronimo@gmail.com> 1277745383 +0200 merge sha20: Merge made by recursive.
8d2239f24f6a54d98201413d4f46256df0d6a5f3 f1401803ccf7db5d897a5ef4b27e2176627c430e Sebastian Thiel <byronimo@gmail.com> 1277756712 +0200 commit: Fixed performance tests which broke in the course of the sha1-20 byte changes
f1401803ccf7db5d897a5ef4b27e2176627c430e 6917ae4ce9eaa0f5ea91592988c1ea830626ac3a Sebastian Thiel <byronimo@gmail.com> 1277806256 +0200 commit: Diff: fixed bug that caused a string to end up as a blob mode
6917ae4ce9eaa0f5ea91592988c1ea830626ac3a fd96cceded27d1372bdc1a851448d2d8613f60f3 Sebastian Thiel <byronimo@gmail.com> 1277999899 +0200 merge docs: Merge made by recursive.
fd96cceded27d1372bdc1a851448d2d8613f60f3 f683c6623f73252645bb2819673046c9d397c567 Sebastian Thiel <byronimo@gmail.com> 1278082451 +0200 commit: Fixed broken 0.2 documentation, it didn't contain the API reference previously due to import errors and a somewhat inconsistent working tree that occurred when switching branches ...
f683c6623f73252645bb2819673046c9d397c567 a4287f65878000b42d11704692f9ea3734014b4c Sebastian Thiel <byronimo@gmail.com> 1278092317 +0200 commit: win32 compatability adjustments
a4287f65878000b42d11704692f9ea3734014b4c ca288d443f4fc9d790eecb6e1cdf82b6cdd8dc0d Sebastian Thiel <byronimo@gmail.com> 1278517416 +0200 merge revparse: Merge made by recursive.
ca288d443f4fc9d790eecb6e1cdf82b6cdd8dc0d 5fd6cc37fd07c25cb921b77b4f658b7e8fc132b3 Sebastian Thiel <byronimo@gmail.com> 1278536545 +0200 commit: Adjusted clone method to allow static classmethod clone ( using clone_from ) as well as the previous instance method clone to keep it compatible
5fd6cc37fd07c25cb921b77b4f658b7e8fc132b3 76af62b3c5a26638fcad9a3fe401fba566fb7037 Sebastian Thiel <byronimo@gmail.com> 1278538933 +0200 commit (amend): Adjusted clone method to allow static classmethod clone ( using clone_from ) as well as the previous instance method clone to keep it compatible
76af62b3c5a26638fcad9a3fe401fba566fb7037 b425301ad16f265157abdaf47f7af1c1ea879068 Sebastian Thiel <byronimo@gmail.com> 1278539147 +0200 commit (amend): Adjusted clone method to allow static classmethod clone ( using clone_from ) as well as the previous instance method clone to keep it compatible
b425301ad16f265157abdaf47f7af1c1ea879068 3288a244428751208394d8137437878277ceb71f Sebastian Thiel <byronimo@gmail.com> 1278582561 +0200 commit: setup.py: fixed requirement - its interesting to see that there are two different keywords for distutils and setuptools, the latter one doesn't read the ones of the first one, unfortunately
3288a244428751208394d8137437878277ceb71f 08457a7a6b6ad4f518fad0d5bca094a2b5b38fbe Sebastian Thiel <byronimo@gmail.com> 1278670718 +0200 commit: Added python 2.4 support: Repo will now use the original GitCmdObjectDB in python 2.4, as the pure python implementation cannot work without memory maps
08457a7a6b6ad4f518fad0d5bca094a2b5b38fbe 258403da9c2a087b10082d26466528fce3de38d4 Sebastian Thiel <byronimo@gmail.com> 1278671744 +0200 commit: bumped verison to 0.3.0 beta2
258403da9c2a087b10082d26466528fce3de38d4 55b67e8194b8b4d9e73e27feadbf9af6593e4600 Sebastian Thiel <byronimo@gmail.com> 1278927490 +0200 pull gitorious master: Fast-forward
55b67e8194b8b4d9e73e27feadbf9af6593e4600 bcd37b68533d0cceb7e73dd1ed1428fa09f7dc17 Sebastian Thiel <byronimo@gmail.com> 1279007300 +0200 commit: Fixed incorrect use of Blob.data in performance test
bcd37b68533d0cceb7e73dd1ed1428fa09f7dc17 24740f22c59c3bcafa7b2c1f2ec997e4e14f3615 Sebastian Thiel <byronimo@gmail.com> 1279110447 +0200 commit: Added performance test to compare inst.__class__() vs type(inst)() class. The first one is faster, although I would have expected the latter one to be faster
24740f22c59c3bcafa7b2c1f2ec997e4e14f3615 a64c5f23c38f06c37a0ca64796368019dfb0d818 Sebastian Thiel <byronimo@gmail.com> 1279130658 +0200 commit: Correct Repo documentation in API reference
a64c5f23c38f06c37a0ca64796368019dfb0d818 24740f22c59c3bcafa7b2c1f2ec997e4e14f3615 Sebastian Thiel <byronimo@gmail.com> 1279130719 +0200 HEAD~1: updating HEAD
24740f22c59c3bcafa7b2c1f2ec997e4e14f3615 17a172920fde8c6688c8a1a39f258629b8b73757 Sebastian Thiel <byronimo@gmail.com> 1279131269 +0200 commit: Fixed the missing Repo type documentation thanks to a hint of gasche, fixed a few more resT syntax errors on the way
17a172920fde8c6688c8a1a39f258629b8b73757 cbb58869063fe803d232f099888fe9c23510de7b Sebastian Thiel <byronimo@gmail.com> 1279197254 +0200 merge gitorious/master: Fast-forward
cbb58869063fe803d232f099888fe9c23510de7b 89422841e46efa99bda49acfbe33ee1ca5122845 Sebastian Thiel <byronimo@gmail.com> 1279533542 +0200 pull gitorious master: Fast-forward
89422841e46efa99bda49acfbe33ee1ca5122845 192472f9673b18c91ce618e64e935f91769c50e7 Sebastian Thiel <byronimo@gmail.com> 1279814094 +0200 merge gitorious/master: Fast-forward
192472f9673b18c91ce618e64e935f91769c50e7 fa3fce80aeb57299633535b9e1c9d08db710ff05 Sebastian Thiel <byronimo@gmail.com> 1281701051 +0200 commit: unicode handling in messages and trees was improved. Messages are now written according to the encoding of the commit object, and decoded using that information as well. Trees will encode and decode their names with utf8
fa3fce80aeb57299633535b9e1c9d08db710ff05 394ed7006ee5dc8bddfd132b64001d5dfc0ffdd3 Sebastian Thiel <byronimo@gmail.com> 1281702078 +0200 commit (amend): unicode handling in messages and trees was improved. Messages are now written according to the encoding of the commit object, and decoded using that information as well. Trees will encode and decode their names with utf8
394ed7006ee5dc8bddfd132b64001d5dfc0ffdd3 c4d5caa79e6d88bb3f98bfbefa3bfa039c7e157a Sebastian Thiel <byronimo@gmail.com> 1283969691 +0200 merge integration: Fast-forward
c4d5caa79e6d88bb3f98bfbefa3bfa039c7e157a 741dfaadf732d4a2a897250c006d5ef3d3cd9f3a Sebastian Thiel <byronimo@gmail.com> 1287134990 +0200 commit: Fixed bug in http://byronimo.lighthouseapp.com/projects/51787/tickets/44-remoteref-fails-when-there-is-character-in-the-name using supplied patch ( which was manually applied ).
741dfaadf732d4a2a897250c006d5ef3d3cd9f3a fc650aa6869639548435ce2760d42c9cdd909d99 Sebastian Thiel <byronimo@gmail.com> 1287135891 +0200 commit: Added test to verify the actor class can handle unicode names correctly. This works because regex can handle unicode, and will return unicode instances instead of strings if required. Its quite amazing actually.
fc650aa6869639548435ce2760d42c9cdd909d99 741dfaadf732d4a2a897250c006d5ef3d3cd9f3a Sebastian Thiel <byronimo@gmail.com> 1287136504 +0200 HEAD~1: updating HEAD
741dfaadf732d4a2a897250c006d5ef3d3cd9f3a a88173281ec56cb378a293d0170e11a1bda96a55 Sebastian Thiel <byronimo@gmail.com> 1287138898 +0200 merge unicode: Merge made by recursive.
a88173281ec56cb378a293d0170e11a1bda96a55 741dfaadf732d4a2a897250c006d5ef3d3cd9f3a Sebastian Thiel <byronimo@gmail.com> 1287139082 +0200 HEAD~1: updating HEAD
741dfaadf732d4a2a897250c006d5ef3d3cd9f3a 13647590f96fb5a22cb60f12c5a70e00065a7f3a Sebastian Thiel <byronimo@gmail.com> 1287139254 +0200 merge unicode: Merge made by recursive.
13647590f96fb5a22cb60f12c5a70e00065a7f3a 94029ce1420ced83c3e5dcd181a2280b26574bc9 Sebastian Thiel <byronimo@gmail.com> 1287139994 +0200 commit: Adjusted regex to support whitespace - it was a little restrictive previously, although there was absolutely no need for that.
94029ce1420ced83c3e5dcd181a2280b26574bc9 8858a63cb33319f3e739edcbfafdae3ec0fefa33 Sebastian Thiel <byronimo@gmail.com> 1288000691 +0200 commit: .gitignore will now ignore netbeans projects
8858a63cb33319f3e739edcbfafdae3ec0fefa33 a2b9ded87baf0f32ae94c10c5851a0468a45f003 Sebastian Thiel <byronimo@gmail.com> 1288198935 +0200 commit: docs: untracked_files is a property, but was used like a function, see http://groups.google.com/group/git-python/browse_thread/thread/84ed1835e26a5296?hl=en
a2b9ded87baf0f32ae94c10c5851a0468a45f003 8858a63cb33319f3e739edcbfafdae3ec0fefa33 Sebastian Thiel <byronimo@gmail.com> 1288198984 +0200 HEAD~1: updating HEAD
8858a63cb33319f3e739edcbfafdae3ec0fefa33 148eb761aeaa4c3913e1766db0a7df0a5b5c8b20 Sebastian Thiel <byronimo@gmail.com> 1288198991 +0200 commit: docs: untracked_files is a property, but was used like a function, see http://groups.google.com/group/git-python/browse_thread/thread/84ed1835e26a5296?hl=en
148eb761aeaa4c3913e1766db0a7df0a5b5c8b20 8858a63cb33319f3e739edcbfafdae3ec0fefa33 Sebastian Thiel <byronimo@gmail.com> 1288199023 +0200 HEAD~1: updating HEAD
8858a63cb33319f3e739edcbfafdae3ec0fefa33 538e8265e04f69bb9bd73a10ddb4e8e9677fb140 Sebastian Thiel <byronimo@gmail.com> 1288199049 +0200 commit: docs: untracked_files is a property, but was used like a function, see http://groups.google.com/group/git-python/browse_thread/thread/84ed1835e26a5296?hl=en
538e8265e04f69bb9bd73a10ddb4e8e9677fb140 97ab197140b16027975c7465a5e8786e6cc8fea1 Sebastian Thiel <byronimo@gmail.com> 1288203452 +0200 commit (amend): docs: untracked_files is a property, but was used like a function, see http://groups.google.com/group/git-python/browse_thread/thread/84ed1835e26a5296?hl=en
97ab197140b16027975c7465a5e8786e6cc8fea1 3da3837fe2ec8152e1460f747d18290b52304868 Sebastian Thiel <byronimo@gmail.com> 1288203532 +0200 commit: cmd: improved error handling and debug printing
3da3837fe2ec8152e1460f747d18290b52304868 2c0b92e40ece170b59bced0cea752904823e06e7 Sebastian Thiel <byronimo@gmail.com> 1288203543 +0200 commit (amend): cmd: improved error handling and debug printing
2c0b92e40ece170b59bced0cea752904823e06e7 1b6b9510e0724bfcb4250f703ddf99d1e4020bbc Sebastian Thiel <byronimo@gmail.com> 1288205467 +0200 commit: Fixed bug that would cause the author's email to be a generic default one, instead of the existing and valid. The rest of the ConfigParser handling is correct, as it reads all configuration files available to git
1b6b9510e0724bfcb4250f703ddf99d1e4020bbc 0d5bfb5d6d22f8fe8c940f36e1fbe16738965d5f Sebastian Thiel <byronimo@gmail.com> 1288208986 +0200 commit: index.reset: updated parameter docs, but most importantly, the method now has better testing for the use of paths during reset. The IndexFile now implements this on its own, which also allows for something equivalent to git-reset --hard -- <paths>, which is not possible in the git command for some probably very good reason
0d5bfb5d6d22f8fe8c940f36e1fbe16738965d5f 735b28bc65964da5b181dffcccb1d05555b5acab Sebastian Thiel <byronimo@gmail.com> 1289033220 +0100 commit: test_refs: fixed failing tests just by making it less strict. It is dependent on the setup of the surrounding repository, hence the amount of ref-types found is actually variable, as long as they get more
735b28bc65964da5b181dffcccb1d05555b5acab bd7fb976ab0607592875b5697dc76c117a18dc73 Sebastian Thiel <byronimo@gmail.com> 1289034759 +0100 commit (amend): test_refs: fixed failing tests just by making it less strict. It is dependent on the setup of the surrounding repository, hence the amount of ref-types found is actually variable, as long as they get more
bd7fb976ab0607592875b5697dc76c117a18dc73 a1d1d2cb421f16bd277d7c4ce88398ff0f5afb29 Sebastian Thiel <byronimo@gmail.com> 1289379557 +0100 commit: tutorial: Fixed incorrect initialization code for bare repo, thank you, Bryan Bishop
a1d1d2cb421f16bd277d7c4ce88398ff0f5afb29 f1545bd9cd6953c5b39c488bf7fe179676060499 Sebastian Thiel <byronimo@gmail.com> 1290118292 +0100 merge submodule: Merge made by recursive.
f1545bd9cd6953c5b39c488bf7fe179676060499 45c0f285a6d9d9214f8167742d12af2855f527fb Sebastian Thiel <byronimo@gmail.com> 1290122860 +0100 merge docs: Merge made by recursive.
45c0f285a6d9d9214f8167742d12af2855f527fb 315c303214cef855499f0c7eda46b7ed82dceecb Sebastian Thiel <byronimo@gmail.com> 1290158850 +0100 commit: test_submodule: fixed failures that arose due to changes of the original submodule names. Also, a major bug was fixed that cased submodules to always being updated recursively when using the RootModule.update method
315c303214cef855499f0c7eda46b7ed82dceecb 7dd618655c96ff32b5c30e41a5406c512bcbb65f Sebastian Thiel <byronimo@gmail.com> 1290158895 +0100 commit (amend): test_submodule: fixed failures that arose due to changes of the original submodule names. Also, a major bug was fixed that cased submodules to always being updated recursively when using the RootModule.update method
7dd618655c96ff32b5c30e41a5406c512bcbb65f 2ab454f0ccf09773a4f51045329a69fd73559414 Sebastian Thiel <byronimo@gmail.com> 1290188727 +0100 commit: remote: parsing of fetch information now reacts to fatal errors. Previously it would just bump into an assertion
2ab454f0ccf09773a4f51045329a69fd73559414 b00ad00130389f5b00da9dbfd89c3e02319d2999 Sebastian Thiel <byronimo@gmail.com> 1290196658 +0100 commit: submodule: When adding an existing submodule, when retrieving the binsha, we will now consider not only the tree, but the index too
b00ad00130389f5b00da9dbfd89c3e02319d2999 8867348ca772cdce7434e76eed141f035b63e928 Sebastian Thiel <byronimo@gmail.com> 1290196804 +0100 commit: Bumped version number to 0.3.1
8867348ca772cdce7434e76eed141f035b63e928 8d0aa1ef19e2c3babee458bd4504820f415148e0 Sebastian Thiel <byronimo@gmail.com> 1290271885 +0100 commit: Fixed performance tests which broke in the meanwhile - they definitely don't run often enough, which is because they intentionally don't have a package initialization file
8d0aa1ef19e2c3babee458bd4504820f415148e0 8e0e315a371cdfc80993a1532f938d56ed7acee4 Sebastian Thiel <byronimo@gmail.com> 1290280591 +0100 commit: submodule: Fixed capital error when handling the submodule's branch, which was returned in the submodules super repository, not in the submodule's module
8e0e315a371cdfc80993a1532f938d56ed7acee4 7c72b9a3eaabbe927ba77d4f69a62f35fbe60e2e Sebastian Thiel <byronimo@gmail.com> 1290286993 +0100 merge gitorious/win32: Merge made by recursive.
7c72b9a3eaabbe927ba77d4f69a62f35fbe60e2e 517ae56f517f5e7253f878dd1dc3c7c49f53df1a Sebastian Thiel <byronimo@gmail.com> 1290288333 +0100 merge osx: Merge made by recursive.
517ae56f517f5e7253f878dd1dc3c7c49f53df1a 22a88a7ec38e29827264f558f0c1691b99102e23 Sebastian Thiel <byronimo@gmail.com> 1290289085 +0100 commit: fixed performance tests ... again, previously I was just working on an incorrect repository
22a88a7ec38e29827264f558f0c1691b99102e23 685760ab33b8f9d7455b18a9ecb8c4c5b3315d66 Sebastian Thiel <byronimo@gmail.com> 1290342054 +0100 commit: Added zip_safe info to setup.py file
685760ab33b8f9d7455b18a9ecb8c4c5b3315d66 9d6310db456de9952453361c860c3ae61b8674ea Sebastian Thiel <byronimo@gmail.com> 1290342681 +0100 commit: docs: added final docs for version 0.3.0, started new release 0.3.1
9d6310db456de9952453361c860c3ae61b8674ea 0b813371f5a8af95152cae109d28c7c97bfaf79f Sebastian Thiel <byronimo@gmail.com> 1290358083 +0100 commit: Fixed API reference docs as far as possible
0b813371f5a8af95152cae109d28c7c97bfaf79f 94140bbfc523ae13e1e8045ebfed8a76fe0a1872 Sebastian Thiel <byronimo@gmail.com> 1290372438 +0100 merge structure: Merge made by recursive.
94140bbfc523ae13e1e8045ebfed8a76fe0a1872 d01b428dbac4103b4f7d7b8fca32e01f70746c53 Sebastian Thiel <byronimo@gmail.com> 1290372442 +0100 commit (amend): !!WARNING!!: Directory structure changed, see commit message for instructions
d01b428dbac4103b4f7d7b8fca32e01f70746c53 db3423d1eab11d00c5475e36eae8952512b07f4e Sebastian Thiel <byronimo@gmail.com> 1290373147 +0100 commit (amend): !**WARNING**!: Directory structure changed, see commit message for instructions
db3423d1eab11d00c5475e36eae8952512b07f4e 5ed5b2011ec7cf72f19e6d53b588eea4adca68e5 Sebastian Thiel <byronimo@gmail.com> 1290373168 +0100 commit (amend): *!*WARNING*!*: Directory structure changed, see commit message for instructions
5ed5b2011ec7cf72f19e6d53b588eea4adca68e5 470d4a7cc865d2702c326d9d1d1b0ab7afb49f0e Sebastian Thiel <byronimo@gmail.com> 1290373186 +0100 commit (amend): !##WARNING##!: Directory structure changed, see commit message for instructions
470d4a7cc865d2702c326d9d1d1b0ab7afb49f0e e088424eb01bd47c6f0d313f465a21ee742e6f4a Sebastian Thiel <byronimo@gmail.com> 1290373209 +0100 commit (amend): If you use git-python as a submodule of your own project, which alters the sys.path to import it,
e088424eb01bd47c6f0d313f465a21ee742e6f4a 48a17c87c15b2fa7ce2e84afa09484f354d57a39 Sebastian Thiel <byronimo@gmail.com> 1290373245 +0100 commit (amend): -#######->WARNING<-####### Directory structure changed, see commit message
48a17c87c15b2fa7ce2e84afa09484f354d57a39 fca367548e365f93c58c47dea45507025269f59a Sebastian Thiel <byronimo@gmail.com> 1290374761 +0100 commit: Changed version to 0.3.1 (removed beta1) so that other projects can actually depend on git-python using the setuptools. Previously it would claim the version did not exist, probably because the setuptools are just comparing strings
@@ -0,0 +1,3 @@
4c8124ffcf4039d292442eeccabdeca5af5c5017
634396b2f541a9f2d58b00be1a07f0c358b999b3
ab25fd8483882c3bda8a458ad2965d2248654335
@@ -0,0 +1,51 @@
commit cf37099ea8d1d8c7fbf9b6d12d7ec0249d3acb8b (dist=2)
tree 01fb5ddba393df486d850c37f40c9a87f4a28a14
parent bfdc8e26d36833b3a7106c306fdbe6d38dec817e
author Florian Apolloner <florian@apolloner.eu> 1218480521 +0200
committer Florian Apolloner <florian@apolloner.eu> 1218480521 +0200
use shell=True in windows (git.exe needs to be on %PATH%)
One bug remaining: git on windows is returning status 0 for `git this-does-not-exist`, so no GitCommandError is raised.
commit 33ebe7acec14b25c5f84f35a664803fcab2f7781 (dist=1)
tree 960b40fe368a9882221bcdd8635b9080dec01ec6
author Michael Trier <mtrier@gmail.com> 1210193388 -0400
committer Michael Trier <mtrier@gmail.com> 1210193388 -0400
initial project
commit a6604a00a652e754cb8b6b0b9f194f839fc38d7c (dist=1)
tree 547e8af2f10ffa77c4ed4d0a8381e64141f986b4
parent cf37099ea8d1d8c7fbf9b6d12d7ec0249d3acb8b
author Florian Apolloner <florian@apolloner.eu> 1219330141 +0200
committer Florian Apolloner <florian@apolloner.eu> 1219330141 +0200
fixed unneeded list unpacking
commit 8df638c22c75ddc9a43ecdde90c0c9939f5009e7 (dist=0)
tree 43a63b045e538a38161c8da5e154ff1c9436ea4e
parent a6604a00a652e754cb8b6b0b9f194f839fc38d7c
parent 127e511ea2e22f3bd9a0279e747e9cfa9509986d
author Florian Apolloner <florian@apolloner.eu> 1219330182 +0200
committer Florian Apolloner <florian@apolloner.eu> 1219330182 +0200
Merge branch 'master' of git@gitorious.org:git-python/mainline
commit c231551328faa864848bde6ff8127f59c9566e90 (dist=-1)
tree 991ed402b4f6562209ea56550a3c5050d1aa0118
parent 8df638c22c75ddc9a43ecdde90c0c9939f5009e7
author David Aguilar <davvid@gmail.com> 1220418344 -0700
committer David Aguilar <davvid@gmail.com> 1220418344 -0700
commit: handle --bisect-all output in Commit.list_from_string
Rui Abreu Ferrerira pointed out that "git rev-list --bisect-all"
returns a slightly different format which we can easily accomodate
by changing the way we parse rev-list output.
http://groups.google.com/group/git-python/browse_thread/thread/aed1d5c4b31d5027
This resolves the issue mentioned in that thread.
Signed-off-by: David Aguilar <davvid@gmail.com>
@@ -0,0 +1,8 @@
commit 91169e1f5fa4de2eaea3f176461f5dc784796769
tree 802ed53edbf6f02ad664af3f7e5900f514024b2f
parent 038af8c329ef7c1bae4568b98bd5c58510465493
author Tom Preston-Werner <tom@mojombo.com> 1193200199 -0700
committer Tom Preston-Werner <tom@mojombo.com> 1193200199 -0700
fix some initialization warnings
@@ -0,0 +1,8 @@
commit 80f136f500dfdb8c3e8abf4ae716f875f0a1b57f
tree 3fffd0fce0655433c945e6bdc5e9f338b087b211
parent 44f82e5ac93ba322161019dce44b78c5bd1fdce2
author tom <tom@taco.(none)> 1195608462 -0800
committer tom <tom@taco.(none)> 1195608462 -0800
fix tests on other machines
@@ -0,0 +1,7 @@
commit 634396b2f541a9f2d58b00be1a07f0c358b999b3
tree b35b4bf642d667fdd613eebcfe4e17efd420fb8a
author Tom Preston-Werner <tom@mojombo.com> 1191997100 -0700
committer Tom Preston-Werner <tom@mojombo.com> 1191997100 -0700
initial grit setup
@@ -0,0 +1,655 @@
72223ed47d7792924083f1966e550694a0259d36
f7cd338ee316482c478805aa8b636a33df3e4299
994566139b90fffdc449c3f1104f42626e90f89f
e34590b7a2d186b3bb9a1170d02d52b36c791c78
8977833d74f8681aa0d9a5e84b0dd3d81519774d
6f5561530cb3a94e4c86454e84732197325be172
ee419e04a961543444be6db66aef52e6e37936d6
d845de9d438e1a249a0c2fcb778e8ea3b7e06cef
0bba4a6c10060405a94d52533af2f9bdacd4f29c
77711c0722964ead965e0ba2ee9ed4a03cb3d292
501d23cac6dd911511f15d091ee031a15b90ebde
07c9bd0abcd47cf9ca68af5d2403e28de33154f1
103ca320fc8bd48fed16e074df6ace6562bed4b5
55544624fb9be54a3b9f9e2ec85ef59e08bd0376
e5c8246dec64eccad0c095c67f5a8bbea7f11aca
1b54d9f82ee6f3f2129294c85fad910178bef185
36062a1634fb25de2c4b8f6b406ae3643805baf5
0896bb9b8d2217163e78b5f1f75022a330d9ddc8
6646dfce607b043ab7bbe36e51321422673b7c56
f0bad592abc255fabe6c6d6c62b604b3de5cdce2
5705e15c71f9e10ca617c0a234b37609cfb74d23
b006d8b73912eb028355c49e7bfe53a29f97ce7c
b21eb6173dbe07cac63f4571e353188dde46f049
a3256f3150ccec73c50b61b85d54e30e39a65848
c5a32e937166d65757f3dd4c1b6fd4f5ecc10970
1e90e2c654aab0d81088f615c090d6d46f03ca4c
924e7685fcd62d83aac19480837e4edd9c4bae5e
489e1468aea658a333481132957069708127c69f
970b6b13726889f6883e4347e34d8f9d66deb7c9
df74c45e8fdb4d29a7de265ac08d0bff76b78a83
936aa0b946155b2d47528073433fc08b17a4c7cc
3b6a5e8f12b6269a0a3e0eaeede81abfb3fc4896
8e0f306dae96d78aa1ea9a08e82647fd95fc1a74
5eb099e5e274be44c0fd27ce8928d2dc8483dab7
050fbed693d4806ac6c03460103777b2a4befcf8
c5d4b6dac74e323d474fa8878a7ea0c233d57019
8e5daf911943d5ef025025c137fcf97164467141
bcdf7c2421302b15f4ee4ebbdeae7b644a4518e7
e2874a42835cbb2fe8856a398f5c4b49a9cd8d30
f50ea97159e4ae7132e057fbf5ea1e75ec961282
5dbd614c20e9473240082239894d99c24de42282
0490e1ac1ffafcb9117029286b224ab39671a015
ad3620d47f0ea96f24904227d3c7a7f9548c34dd
fd37e7191ae3d312ced0877a1492cd2ea4578275
b7f8cc51c9056a469006b5601a4993b67c07e099
1d849af5083073b8864487938a9a2a8e21d71529
26d0bb4c9ee3d8591fe291c86f495b2d1900bf9b
7a25e3056a7225c1ff8542c2c2c1cf6f3a8e13d4
d0e0de0b13b9c81d2bcf9d54eecdb20591fd6d2f
0bf82343ade1e07c0aebd14ee66df688a4cc0e87
d81de0fb6a19342a90cdba9a664659da66296162
9105667175797bbadea80879e98a5cf849a40065
12f5af2a169c658cfae1677ceafd527d3775873f
00ae94689600b5949bd0fcf68205f31f95a36aa4
8f5d34224e4620c51c16c01578786e76567d025d
3385eb31651c84384b4c7e93d82bc5b592edf9fb
eda9179b9af0275d62c4074480e7a0103d356435
982c2d1e55165fddb4f4c69065e2c4ac39542c84
7117495ef012719769582939ea59a5533077fc8f
b7dae75dab5b59a320b8df8a67060d238fed3a8e
37c684e1a46599fe4d34d1601875685a70b1b879
0a694fa0cb044a31bb844564776b490c151ac317
e77c6b459f01ce078aa59183189226a6d48fdf38
dd0c0eaefdebc38610bb1986e51324a0392e829a
d8bc2414e9504172da640f29db1b2d29d834a94b
a9f1119667dd0f5aa9413dec23965a747d1dac05
f52775f6bc21d999382f4b9b38b108b814114ea1
e82c77ac679887140867e471a9f47fd3b2d95039
2db3fff5673bbd4bfcc8139d8398727d243c9efd
c1805c000c6233a20ac0824cad21c1fe40f93100
83f7807585cf70018a9a06179af9d89d4a8204b2
730c326beb29cc6d2624915b125633792a40ca36
bea422b653d74dd03ec068dcce938169149aa822
586a57666618299464519c450033eecc3ce89664
82fba8cf4796f2adbec5ad336bd750ad60a075fd
9d9b899f836a199fe854075e99091d1ef083de24
4670357c662596aa2c2922d826de84abd9f877ea
9b562567430544c74009ea4a6173f44ddb4a44e5
013d51fbb5f3a60bc748449b1ab73158da9a3203
3fe67cb90fca9ea76292deb793cb480f4eb5e8d6
91c80e489fee08e71a79bfbea79fcc28e1aa27f2
dd9104095bdb08fe399af46d91b334e760986ddd
a9198904586546a038f855bc6fc0e7cc413722fb
574a7bad1017d9ed466474881e1f068f892207f4
f95acec9297b7816284d8b24e984cd5c82104c89
3907dac65a125b7759172a8eae959b0e70220299
e5b44576eb2182b16c7b6770fab5977eedbc03c6
9f4aad9833d0f9a609dd2556e7db784ba813d8fa
579309c96651a1fed75fdd18f80019db8e6624ec
5e1a9a48e6c96099d6a0c3aff1e31c9be16b7b8d
cae4b811038f4e0dd4a8e68122c3db955e10ae81
fccee1c818f5af5fce593de0949f5a8ecd35b443
d4187d5a5f9ffe1f882c74f6ced7e0ba1c260ff2
02ff197aa41d892e623dc668b0055806294bd6c0
3f81af24214761a6ed77fd4dcd6e45a651dd8f84
5cb08c5232a669a881606a6d8c4a4cd23aad6755
5212b25869e0b9aff485af6f5371a159e89f8f07
a778322bb60f8438a68112a73df78e05a97093ff
b55c30c3992a766628dfc4a7e22db4d8d9e46b5f
1d3e4a32e0407f16f641be316c745c1a48f16e2b
7f35ca3333944165e0ec82a3a95c185f67fba904
ef6c5bbe2dffe76e4a9698df03b8ee08af702033
aeb90405ed696c1efcb29d0664b43a52a2bf824f
e0b8bebd78172229666dfd425933f9bc21863928
2a71a55154edf75ab51dcb4f2f7dc63592410e16
a5d25352d326c77d083a2e089c2d80b4ea923624
a3fbc38b9f1b86bb5f5e6540048083fc1dc6062b
bbe67e1bdadf4aff186769145a40727f78e39e01
a02a58c6c6d04001873ba91ac3dc902275879d0f
eb5281d4f40e18b0e21d275ee5c5964bbbcc855c
19e9939a098b9cb93c8c1d0d069d46861afb507a
7a72471f9a4587cc4a7d37da0d26122b0eadaddc
c6a043eb057cd544130b77bf99f39b7738e0a204
723b6223726c6772e034d9f4ba5c710e66a1991f
25b4ff1a26dd3694a98c1ef2eba04a5a500c0b28
7c571ac2c35a7e1f399651242e904596c93beeb0
0c90015733521720688bfcb59ad2a3978b2fbbc3
d6b99183122a97a590e4e54f4617b58f13b90df8
6b663271af39d69082422866e61ff7801c2b3fa7
2e9e6ab7651e4c215110eb381678e0ea2bc0f7d8
967b91e045661c9b6d2a5f011ec152da391db7ec
7fda8d15bdb3d3d61fce49413153a216849721f9
f7d7e83ee1cec103a768ddc9f68b6d5075849894
925953da542a9c21a3fde1ab0891175fb6212a12
ea2f54455427506583437391cbaf470a1ef4edeb
f0bdb2cdddefb3a391ec2e3fa9b78ed06d7c874a
8d289566fa92a96a83ff3c2e24c1f3d12b1718ed
7fb102615532952c6833e87389668831b37a13d6
7f7bbe8473158ab606a89ad66d607ffd0e5ba1f7
a98ea5a00d19406f3e644448039f13db496cefd5
39f03072d9d84d622ae974b09dd11cf7a2515a7c
e2050a1c488fff4b114614d7f75096dd0a149f5b
d2851f113530fbe211b3e948b6181152d30d1fa3
1eef0fe740f6db35a91e790fe77d4ba1c9065e99
9608403b012908cc58223db44962553704cab8af
4911a005ea6b55f34f8b0f504a6a0934c0df896a
a4400fb8e7d0f1261634dbb89588da86b8b6c93f
f310729583f6733ee60f534a9732b7a3a9e414d4
49e78793487ce4d8d7e624b5245fca8a9cc1ba66
2f2501ce5d28e5ada6018504ee8dcecbbee70428
f1e127253e1eb07b537b221e9cc96beb16333790
8bf1684ca9b5a37d91671dd0d63d0ac59bea987a
24838a6042a134b11fe945bbaa5ab1b2b3fc6eb0
f53c57af21fded3735fd479b3785fcf7adf80268
aa8d0a63d61d13524b1395972563b516cd263f05
16803d64332412a66121ef3fd10cd0d88598d3be
5f2715ed4d9416fa4940c2cd29b5ca18b6a79b8c
851ede1f8dceef7d681f35e4769e5693160c0a04
5264588c6c20c38d54394059eef0a854683aa3fc
111800d8e66ff86f0757df7eb6533fc62040a22f
b04de89d31003e468c191cd08dd2a4629d99c38e
6aef629094e9ee6b4fac2431897844c4dddf2f57
d1168c999fdae7d1eaac8c163b2b1190afb1815c
6afc3257929528d9f4de964e8828822d2fa2c93a
436f30ce1b562efe4f34696def45b0145eb98304
9afbf904be0e6154f6c424377ad596e86ea38807
a3cf657305d9283525711e867e03684a2e4b39cc
5813b4d04b25c385359af4437027b4fe763cd2ba
0fa594594c97a0d3579312f4ec073304c1436106
cb7b36c28adb38b1e597fa3f3b5c24c988a25b0e
5b0c867cbda81ce34df1b5fb67557b556ea24e9e
44090e9c550c7c5ded01dc2a681a7c934ba901b6
9ccc89b61736c4a9c02faaa679e97a9ec063dd29
7828d6d18115b0720888a45e3b547b697910c59e
618497e48e46fdc00dee67c07cd0f40860e819f9
69a14ed4f36d880e8322a530d8c5bfd9888a8c13
0a0cd655e40903abff4840c23b57628fb1a88122
cb262098646f47e1d80a89662f1480c216bfd81b
d60e59fce6f698a8bb97e2b4a935c069584621b1
ca77ba0d6d301cee1d45edb24742dc5cdabd4b83
17b598510967922690f5181903f20ddae5758e86
30ad3d9f3164966afb2974640f772387fb796b7d
48964c5dcc94234dea1737d7fa23220f9eab0fb7
0fe241f4db12f455c2f5976c6bf6497cc485f503
04953aca41bd372d990da7f68cc795f4a8b78d94
2dc9a061595a291d8c53168c42da8d14da52d610
68b15d34903038e3f2e327f03f0486b2d38784bc
30ceaaf39b10f9f9c7b4362505144d1650035a40
e75891a5760f6a51f54a33b671951c16fbce1558
b2a35989ad3392f26e070b158f89d1d8b75327f2
8468830b8b37f7c1cdda926a966c0aba2893a7c0
6a6112e8cde1bafebfa12e4c863dab5834c38e12
eafcd2ffc25d17fce41eff2afd5c4521730a23ab
f7eda0752f45c3a4eb13e075b24b86d7e7dd5016
b634d0d48d0a113bc060a47972b10c9353621428
49f95235a174f0a056e44bb5c704fea4ab345353
6eec70a31a6376ffd7d6b143be0968a195ad59d6
7c9ae1a71aa39efe28a678c18c8a03d759deabed
a19fd6f13c16720dc38a1f572eebf960022015ad
87052ac2cbaec195094e1d1a2bad4ac480bd111e
2cde1b0e69f97a8a67bb47d729c53af3ba8e5700
91a06d1a4efb6376959c3b444a536fe6b4fd4d6b
07f73b465b6c509b337c2776fe7a73b56ee178ec
15218bab55236d62fb8b911c2ae1ee05dde1ee60
900180ff2aa70e7d857403412753df6384553d26
a9c43cbeb0542cf6538fe8025edc8863d2526c68
d7d8f0c9b7d56f554d5a5cf5759f30cc3d36771c
d703e5d9ac82b8601b8f4bfe402567b5ce3ebbf9
3905a12ad511ffe157cb893e7769f79335e64181
73a933454b09ee48ffc873b0ee767e0229f2d609
c2c91403aa9d95efa09843bffe83ace4d52d3446
c90f480010097efa3fb7046abe7fac3c9b8b3975
13e888d5624e8087ea63638de7f4375f5c13ac55
19344e551c8c5e56e91de14253b4f08ca05f9e69
b1b8f098bb1e2f0f08cf82805d7bd29d2931f63e
3a3e025bbb2f3e49facac00e270d8afa5d31b962
195116405307f7bd9575f9621fd93344304341d1
31252094210748399f7e43e7b6149190990f4e8c
357e549bf43126e371a1f85c393d2560597cb32d
df1f8ab23f915420e9c48744decbc45375f180d3
f96c2eedf6800b8fc31032a02caf0d2b469ba9ec
73405f0505813ec1bd25f03f2825315f3520bcca
7e2447536c35ae67e3737a031fa1ac57026475a0
970d4c4854dbcc3b0bf9b16edf1d47eabf3be242
3c73519e6b54d3559555ffac40072957041f62d4
46d461676fc1fb16fd7dee027065441d9a8b87d5
f11f64bb55240dcc1767a1ec823aecd3531f1d20
038e91a424078c5d81cba6c820cd981f0be6086b
157d6e98ba894cba5582caeb15b429ca0dcbf2d9
2c768cf9d1bdb6d3d84f662a847966b69c898f59
4fd0f29459ec3ea65625b943b147df85e5826cd9
c7e90c64e580ce5f95147eb4e117b56b5cda254b
cd4f2496b274b0d55b7c48388c2ec0365d9bc266
68b5e288a29ebbcd65e6d0a8eed47702ee4e689c
22abd4a7ed7b061364e002f1fe08857850a309ad
4c3b38be6fda8ba32fe6f29226539e03bd0c55ce
355e946ca8b8a5e4c17317446b12fc374399810a
1fc5c0122fffdada1630febc1f2e42952cdc7e2e
8db042e1faef7be24d62b9287fd3b9add7a1b4cb
1cbea023ce354939ae9082a62810b46f38ab1cd8
f5edf5b99d1bae09314b9680e58766a4e3c1bbc0
58a5ef79958b58736603f47cf211494fe5819601
8f2038bec169ae6d62885f522202d8171e3f5f5c
5488e29e68684648b4d733e90c6e3188d3bd5bad
84c88e813117db46c6ac68b16a7739018eb99e24
789c3655197585ba8771ce68c0117cbdd41ea390
0510404a3c0d337763e90e5315548043bac65b06
2a665d7c6cab59ea8e3bb7fc65249ee947e51fec
d53423de534d3b5e68a7644d4218d835a8bfe6ce
73f2a3f332f23579a29e090f70825dcf84dcdbac
f79ae7f27e750c97c139cdbdd7c3223b39ed1a70
c84a75f7a4b274c5c133b1df3648a5a24ed9f687
cdf8e5a49192b81bcd39d9f4e39aa4812b58b80c
1180461f564674e373222fec3b4fe8c2861ea6a6
150d93bd910597b85500e74b97b96e7eb4bce2f6
ec3b819ffe3392bf193483fea94d4404c88966b1
729fc8ffd38c02a9576640b56376c36b49edf52e
2ee31128fbd86244d547e3ff66b802dda699210f
f87f28c563ad602cba605e84bee95693b77b8840
9e92c5fb59af58867acf5512e95138fc368f7dae
76b1489042e1bb45909832f7064f9a5437b68b18
66f5d86face564c095b3c95848f070f50fe4688a
f9b2b3ec52b88dbd68b2f2c6b246bf07f632b40c
14f689f05c4fae52ac8bb95762ff43b9f7f4e567
5ca84af5f7a3f4533b353c43a332b552cb2fc5e4
c5f33e9eb55201c41691e14fff0d45e32c989a42
9f83cf471949164a6352cb9e3a201b8bb317b89a
5532c7b06a2f02e9cafd6673d5099798c4144690
0d28c20ab4f03b5d8579132048c060affc36c466
cddce1dfd9d4d7f1fc49003aa211f018bf8fad2a
169617e3672bac804a271c0aaff9cdbac7b4b45e
fdebb28d6ae398ccba88f3e2e63ef6d7f10f62f4
0651bfe384a8d5865d6cab808ca0ce803af93878
de89eb007459fd5400cd344dddf240fc33fd0b65
c6a14beb887170d8c901e522f2f4dce3bf0b9ed8
13dd0647b3ee39fae1140f8eff2b15d7f63ee546
9f89105c1462f2a80e620ada1b95c3d08a121c3e
1ed6496751273cf472538779266dcc3dc9797192
4e8dffa66fc7be8f864cb48cf26abcef4cc32379
5543fce145ff28a1c424b730b376fd4e3cfa0956
bd951a4a8574baac21b7e1f3a09d1265aa51850a
3fd1c12fa880ee45b0ff7b794238a8894306a790
830ec14bc9edbd2c6522ff46ed0acfe477e7e32a
e68c3109a709e2b732d0945f860495de161754a2
1e0f4fda735167ff6d27c76a67b8b4a4ab31aaf6
c6c40dd0ff4420708c2e0f5a0e0dadde93eae336
baf0c18ac24acb9ac3d1a7c0030ad5675eeb64d7
8d30906e9f2f68024eb716be9f482de5cec5b302
ec9fce551828795e1dace26a11f57f9aaf1af37a
28fb918d7e9840a7118b7aa0b6151b496f1fc1f0
b9e58c5b98f7c89054ed5c0a0226066ba9d93c8d
0c5db457cdd3852182ce70b96cb376337b8ad7ad
36a48168274cbb6f31c35777a74ee16c06e1a853
07ef3ad40bb01bc7798b241c88fda2eaba7aad19
02aa9f2ba871e9639891986a97618e0917955fc8
5f776d3c74ad532f36ab75a71bcbece6a62c831d
f31ea9eeea91106481e1b2d30026b601555b6699
c3d7f6bac18fbf8041662fbdda4f04e3f3b25e3a
6280c4bcf1195c011d7a7abb5bf689df11d66419
45fc4ef9adaf514bbe21f496cdea8869a147c81b
fa1160786e34c057cf1212efd59a72c3931eb2a3
09b285cc7d7c8768917c7d4e5513e3e73d752b68
a8da5db6094c887f1087162c5ddfddf601560523
b6134a31d236c376193e969a2df65c8427d280a0
793e0d19fef38f8a151622257b13edf6786e469e
e40e6a17b4df5be46a2cafaa3fca5f4c3cec5001
4d82e160cb874da6dbddc27af7dfd1036772b8f6
745ee8e3e74dc0674dd8018999707f025a9634f5
f507baf298549096f08dc33de22f7301e9799814
bd7ebd663da867692f2316b94db73c42c0f9a5d1
697f07726d209cac519b528018559f8957c56069
2297b5172c0c1c83f2d78fc726fac0803be6eeb9
91e3543f82039a446c5be8293d5a79ec767d1444
e997169214440256b5b759f6e7e255a302838c97
77d174ae14afbc6e212eb7d957b11a231a036d96
3e81ee29892006f16d5f1f26d9d6b341a8958fb1
59957e1d84f8fe8117d9697154c3951ba2959480
96c6fa03962edb98a9b6aa7793be4ff54e79bfd5
068a293fd6b4fcf216fb84ca982699095613af37
b3b1804ffad1b7d274bc3f8f5aa11b15049ac030
63e394c13a50de0d9f6cef55a8c91830200c3dac
e7ed33eba96d590bbc7179fd26db707c910d1dc5
6b2084340a988f4123e71c6e30817806ec4cf3a3
da721d3f48f821faa90d1a4778d77b03fd3dcdeb
a433cb8d56a4fcd50bfc74b0204c916e08c9d5e6
067fae6fd778d5b1d6b6436aedc0d25db58334d1
e34c192a5aef80c7e83c78c2372602830671ca5a
861a44dc56a983262caebf909be96c62254930cf
417ed493a824863e30922deda64b9729b1c6d6e7
2df6a0d803ac21f0d20ae9fce0a970b35b3663ec
44bedcfc59292d3ff6b36759b324812fcb779b2f
c620f7e60c8ce4ddee8fc1072b2a161fee862545
82ce5a39b422aec7572d9a773f85be8eaecb1618
dc0ea6defad83a0569896a9c23f11f5052a48107
e1c15f1da71a3aabdd43a8ad669d2a755f315c77
c78ee1aaa5c499019948c9a3dfca3aaa2f897860
e66d0d34c541c6588da3ae06c6aff7e7c9ef5745
c24d513d46b3db5b4c53b36b7e43ce5fdfd5a2e5
a75d0a4bf6d2e1a9c4026586cb707f254691eb3b
41e98ebf4526e78d78ab16182b503f237e77fbd7
2182dce8c27c33f9452e7c910f59750d1e58b1e3
6b7aa9fdbdd0160ec29b6b3b591169c627fd0f01
b39470063e41ee5773f47de325a845666d0721ce
c7941bdc8822ae1842d2a2f42924f31d2d37e864
fad6e836009429e88c788ab7e7a679d422d8cae1
a478917ebcf70c5dd6f56c7cd139832108696189
4101b1ae3b17e229c1a80d9c302b74d215d98f04
b051ec4e69a99e26d6a6e5d7a393014f841eed6a
5298ce551a104605b7d5d9872387f3eb704fe5e9
b14a12bed26d53eaccd1a2c172ca4a38773e1d45
4ae0790397d05a758013e0496ba2c2b23363361f
431f01bf3aea6f8baaf06669172561a3ec9e82db
12476263aa193c7e921ad4b183fc648bd73d2a1e
8e937050fb12a62e99b0cf685578213552774cc4
b85a487787454f6dac84be59f905b8c929f0ee94
dbb2116e0f03fe6d84d2158d67ddd02761938bda
57186ad57242ad0bcd737c4ca4ecb7c063979a95
cdb4a295593cb3ad424b4ab86d74154d7bbb97bd
8e9e1ae0edb776f0a490005b838f8ef82b368be7
73f8f21a69a03cdc2b1031bca214a6b84f4c867c
b913c6d878ac5cf570e6f8ba9b5ff022ed601a8b
b98879530fd51f328441d33c64c6c5f311097e15
325b21b5370a0c179b40fd596b9daea00b3615c3
6e722a5c5393dda24172de6f8e08138bcbfb10b8
44b396caab82c97a6270eb7391d6f96502c9fcf9
4e6ed6d22079b68551bbb83e5dd797517796a438
b611fe79daa20893683475cc459dff98b2d4892b
017d40f9b23f4a4379c74ceeb89ce7b4bccb7460
a31b0a7fc7190218136d6ff6ffb3ee6af3244135
861bd42abb90a61ed757728e1fef7cee2d6aa081
6e9ff586de744d166a9f6f213b609e7386692472
a790ca7384982e872092766c036d6faa86bff71a
13485c50ca4dfd885d516154421bdb23cb034230
c5471e696f3166942a245e77796bcddafe6a607c
600308daf62d0d651fcfc874110e7bd4f5de648a
bada607744ec7f37ba9d05c09bb8f41e7fc3d06a
d3b230b209fd7c3f4a39db965b239ab600fac1fe
6d730b7ae0b662b1f987101e8ccf9c1828554d69
f0757668fcd3f8d1f2fe83ce9f0e2355b6be75f9
40819d9a5631a184a17d38e36240d1171a6fc923
8a6847ca68ec998df0543c4b5bd5c709c05d5f12
d8eb0646ae1360b5b984ba7d99bc64e00dd67016
761bf1cc1e2b86437e71c9a106fc9c341097c3bd
3b620d960d29fa7719f95cf945163b04e43d2dad
6be8590f72c2ef158202486e75f273d8598be6bc
d7f22a15d66139efd65bae28ba780b0bf8d1a914
e1ebbf612cf9d49cb08d0e0770ac1678ce1436ab
4db9912f07ce63e4519053f52dbe521ec95c0fba
b9fd4f4760ef65934b5d38e8b7c0eb2f77822861
0e0178ecfacd553526afd221734607971b6911f1
8cd4823a8ac9f846930408ad1759da4496384f9d
e96cf22a972cc3185739ef1c1ce74a978ab71d11
a9d63829aa54049801d37429b597eb04c9e1412d
2519d617e18fa35974e20d10414f1262013501bb
d02fc8d8483903871d9f65261b32c6acd2e4362d
569456505d5c97934344d4f989a08fdcdb522de9
f56d4c60ffb8df8fc1516d32a0512def0b6f8296
745e899452ec746d3ffbb7b082995b7939a85387
8c11f9ca2433bf9381840696218c245ec700666c
bc2a868d1ba12b485a6eac460cefee67bd9ee899
e628b072d054d982ecbbd7aa7fec628e0d9ee8d6
e3390afd65e721dd8ef228f48fd4244228de2986
35102507bd653296eaaa5e7d475405cc1feafbe3
e2e5342f92148238391665fba101b1ca7dad5582
621f4743f0165c6ca3f1571773867d2e0da67961
5f558819695a49bbb92d5d1e07b9f12072874024
eb45e9da84875e2d1325b78157d2f9e96374bdae
bc0ab7e4f643e779cf9554f03e567d4f4708bd4d
fd55e896d6df035cba49a20e26ed6ddd2d7b6024
dcb9d95840c9a0514f8fd0a7b3b17cc228950c7e
0bedc3d7a01f9819171c0b664e16900d9965c3ae
94f6e372fd90e96cfd9a393a5952aa850485de66
0b889a9cf37997c58a9f8979850da1f4bc84de9b
b70ec5facdea7fc681c2a10dfb14ca0d8fed6f1d
03e0192fb34134f25784a2b14791fbfdf69461bf
9266cc52df3725107edf513aea4a02c131aa153c
0820a412fdc9941567d86cba02793ca6a6378275
f1a72254956f63393f6039a7d5da5fca943fcd2c
abeb9e16d924c1a87c5b525ed12c43031ef1cb2f
d5813fad322c97bc31d7dd37f838c7442aa68f35
428b26fdca0ba98a3a01e89629bdb778dec9e8ab
19ff672db65a7ee25ee0d48baa3f9bbf2d145ecd
d1eb6283ece7d9c814b0d3d5223207b905d3d720
9ba934b83a40d26ebc5e8d7304ff29c32541e82d
9b600cbf0209ad6079d00dd5d6a5270d858d5929
0f22868d790bfab8a41894fc7eca161256ab6854
fba092070b6e03432f6d47154f5ae4734e935a05
11b1bf011fc24c2ca6dd8c81206c7338ac2b2915
d93c82b17d7416e4c57ed036d6b75a323859d837
27f762e8d3f1ed8bd0254800c121d0f16e914c2e
e252d9d270330072e9e5e91257e90f255e7e968d
e55c3c30785eb50b5dc36f9568e6b6ae39e6de11
63491807090d814bd7ffccfe44cb05795830eb3b
8111dbaeb71c53132229c4064c34247746a3769e
8fe37ca0d79dd1f8132e9add06aa206d371964e3
eb32fae4665b9f11ffd06a342e763b9d212e1353
4e923698ee5566143fc6d32fdcc6fb46fcda2d23
2e3910e29142382f9bbd1705ab9c605d1937a1ae
533cc5f884885f771d3f6df4164fbfa29bae0e6d
3fea0404fc58822cfc60d4f10ca404e3223f82a4
733404a081eda804707c3dde1d6b8161e7a34b3d
d2be0ea2923344abed57aa21f13dc816d4537eda
7884465bb9da51c8b6e95a1cbc9888ed696ff68d
6e63e5a03bfbba52dc3b4f504e6bc41951f56707
44a9d3ed75c44e817a6e4b56e30be06a15f453c9
91e12aff0f988bf414e64b97a8c20b9699440309
008119e510f6a7f8714e63d2ec33ca7cd7776ea2
27822b01ad020374ff6169428649fd667abf7f8b
0c972fb8903c656cb7e750b1d5c1ea1f26bd8c50
3d8f3e1fae697a905e87250aa5c0ae1f6c60ad66
744421b6f1d3aa30c7558570da8aa1d52f11d39d
ac017796cd3a5558dd78f73ecb82a6b961d8a3ec
e11f534a2fd666ecd841f657faf0751d5fe02034
eca5d275376911916c3e018c2d163cb8eb914263
a3144ddce360b6ac1b55fc27d19a318be1f224c4
84fc7d68bf3a309b3687da768f0dc206e647e653
fd5132bf8e99230a9074ce9bb3d950cd26b3d25b
720ceb5e566d26803db85af3ef69fc4fa14d355e
e97f338a79e2248afd3a2b9077d8ac1c334cdf38
0173ccf8d04014bcc4cc53df4d6574540f4231e4
52da09b8812d96c14d3e57a77784d56e5749a8ae
5169648c7429788c777947e21527e121d35aebe9
41c8c94cdd1c646296946a00dc72dff8fcb6556f
9b341f77b72b55674a030ad0209ac297e41c5570
6aacd7b9b8fc571e930d18da63efc8be46e31bdc
9875e5d15c0750b6ee4c41b0e1321e1dc0bb7810
fd60909d92b0e124957aa0783ea03471c73fd732
2f299011d707ffd8502e5a597f38f0d25ab3099b
6c10423816abd3b0f327863c9b8fcf55cd6265bf
14cc60568455ac2210f00ccb238ae41ddb473fcb
74cf0e9a42bf241d3f76f25aaed46e4b6550d842
9a0eacdab0398ced7d729f5c7a9b173eada2dcaf
3057f2e5ac8cd11cd018780c062da7c2bb11d2f7
dab224a6b259d9d7e16af4cf7e2718af8ba4a74c
fe6dc165cde8c826a3935b536c8cfd1c10ba7d62
7f3572bca7fd48b66649d761a054412b8369deba
2ea30dde468795a3ccb307343cd50eb7041f5ee3
5d4099ededa31d823a355d4ef0e53bed6b833539
69eb5257143b2de63c8c7471216ba6f025b6d7ef
e4c7387b32e314cca7e0ee2b1df197340272fad1
01f14dd38700098d97f933008327c8456c75af34
94040e25d5aacae0e55c3e9a91fe24d7daaaaaca
cd64f093886bf092b8d88c75ccd2e2f9118d3ba9
ceb96f9512f80188fafc61ec8d8d61c93d51a5c2
9a4e9bf98bd371cee2b69ef62a1189c24cd8baa4
dd861f56b65404a625538978d50819924f384a60
b2960c129e39d30f446d27e38f726975bef7b4f0
8351c6b1293bb0cc4a2e1235995c16433c84c463
008ba61116504d01558fe8afea0d5b3e90944b76
cce20d2824a877ffed6a912e3f22d7db3d8e5043
5e02e12edf58e1dfe37ed770fb32171e64993a81
7966a56b3a3c9c9ac6db5b9355ba5e96558ea7b6
5dea2f86730665894cf03f2b1fac98c1217a9fb4
451a4d8118d2c9c746c687efceaacac799e67ad9
059dfb5adcde569a19a9260c2ff85c7b47f8c516
da7449db2898c567fcfb40c595c0c21536c901b8
db97ce996b09b15049a9f818ce27a680e585bd11
e1f95b9a8fe2394e1cfb41fe83f130bdb68fe6b4
fc2c03e29a331cafc8b08abd5eade336904f40dc
385b11a95469f7477bdcf5b9c743982c4a866c65
d7e31d19b9ed766048ccf9129723ebe36b4842dc
9c9af56fb29f510ef75221a39964c128448526bd
83e3c642af5648aaaa119cce34dfef6ef3c560bc
a831fc506ca30a11c9d9b33c9cb2c43f6f01a446
62c5ebf183a0cc2332f04c1ee3323005a9878438
6bb31edda343bbbc4410e2f780c432129e610b47
846ef94e8af8f09340a740d11c93157c81079bc0
47aec581139d8a3ab4f2969b481868c1485e2ac6
e3f68d2cd84e15063c4f73c8420a444f9fb64a7a
3db1240470361a7314ea096f63c0fde74810caba
ae951371c666cc605ef69b5ca3f5f31d0cd30298
8ec035e739f01aeaa09742a92154f02ab3dbfe93
4737a65f7c1e125ba37ef35acbc6e99c4db2bed6
7005d4cae81a16a5a860fcd3c259d6ec07597072
d98807cb107ad2e9bf95138ee4bfb566bf75cb50
1e8cbd548f12e1ec861f3aed5fa9f080cf2782c4
25c2b2cad9cf873edc80747cd2df5874034282aa
676749cf8f76eadb469289b1d918cb5e485cd56b
8cba76ab8a5034ee21e95a99196f257b7e527b49
0151aa85f5a178da21ddf7d5e81398fff87604dc
f881500552171b5a8a8c3ec7a2dc06e493a1ebbe
8d39edf2ae13ed33d0529164d4e172bd4d060d7a
b5c3f29c81e524e860e5f9ebefdc573f83fc600d
b686bc7a882e461987ffb7bf1a25bdc6f82ccdd3
ebc1f42a059e7863adb57890562878f652922b56
b30835cea58d0b827cb56aaf9e4d5f6e673a1bf1
a2cf1028df49cbf53c57d0f599083fec59cc38b7
6efa045dbdfb4272f075255411f54fe436c31b8a
0c3f085a4044e9231287c11e34504624b04ee7cf
b8e628fdc2a7627283e0601ebfe8e978e91dfc00
d84e30103d59d6bace53223fc0d5787f03d7f028
2e0e70d0466bde79d134a215a399b20c2a9d0981
142de640101e2bee71fe2dc98e567d688c7e3aa7
8b02a5e91092f7363443a1cf96933dc445f0ce51
753c065260b1659c0d8d247b62f6b0fbe986c7b2
1113b6978475c9941be9b140e8cd6bc267469657
0a01d10b21c039484410c7898250afc4079db28d
b9bd23fa584a8f1900ada4addb96eeb750ef0a68
5ecc9b675c4cc5c1bdcd8f84e1a52457ad30144d
d91b0a31122b251998915b4eb274350fd42a841e
a829cb9c850cc75546547aa95fa3ca6100ce16f7
4b9bba5d1063d986be6463e4c5740eb18befc7b6
ffb2f17926143e242efc18b32ee0c630b5447687
3feb18fbff52f17a541abb1ebbb4894beec18d55
4acbde9bdb24bd802ba5bb0ebe19d71c8d753240
c9dba689c67ad7b16c8f6b1bf1bd382369fdec4e
ff956cafd71e4787e9ef7b64725142fe8838a65a
e2c090f1ca171b51d08e6ecbb74b27410bdfa7eb
73aa4812a2effb88bb64a42f93713a54a88e1ccb
8e0e0c69b0adb9a65098b18a7b96d6ed3a43940a
5dc8620cb17c3e606b635f8f95ecebdd66af04ee
18f8afd6fc87b3731145f61818f23b4b766da703
0d2d0bd0680557dc28f4f7b23562495cdbb3afc0
94da53667213590ad9767b335a9f2e51fe1e2c5d
c6cb97a42dcea5461a2931b097ddfd53b9cc5870
62a3d5192232ed847f3c7810344c43607a361e68
aa6992567e763a0b081e6bce753cc42bc287e9d3
1d67358d33250d456040091d8b29083b1b47d9bb
65d399a4ac7dc36df20b8b2bc773bbc6fa67f43b
acf7ea014fd1b7eb351dc6946b199ad2cc98f845
7e4dcbb7f0fc2b051e33b555c4fdc67796dbbab9
a07916245a9c21f3874a7b8c898638ca3b65df42
bb7368d9b07b02aecfbca6d01788a7327743ffed
60454c29275aac27c450323f0141d60ea8202842
c4d0ff10c85ca4c12ddfda1830cee475408205d5
a5da3671524fb761552a4eb5c1e27dd433f80fe4
43142e711f392ae1bcdade749dbaa9dd98664228
7aa0bdd118c78d8929e737392457d14f87d625ae
be921331245c4e04ef9f0ff7e359907e2d101cac
d6f654de1b8c27f84e34fbff12aadffb30342465
fef2680b335ffd861021ceff2a2637f5a360f037
79de53d3b87469e21d510ed6ddb33d809c05a3f6
475b10017d25db725e73eef11ca789ad7dfcf4ac
d14f3734dc27ecccfdb4683cf7ef3334a5a70b3f
f0c394dd6a109b97ba4a9ab16cc71b789d9ee38b
a57cd5c8278e1fd6fae6f02947c13880be4f3b62
83c6e4b636f3bf115955b6eeb3f91a5689e7f00b
b881752a8cc16f49ca605bf6a35af106e7e19c9a
8362e4bcc30e73460ae1b9731bc545fd2b12d8f0
b01216229149bed7c110221551353b54ff8e4704
10ad0e68785b27bab975868b83bc463b9c9c9153
7a66612abaa223ef0410fae66727a8abac3add03
8a7bdba957536b078f0421faf5dfaf8d65ff5add
defd6d03526345a410437eda15cbd067124f9c2c
f7e6c29aa4d1f7a607e0c87ea20105afeee0372a
751363e461257a4036a8f2aa740195401883c1ea
a8d66b5855eda5abf699ebf9c6dd721928007fb8
35ca716114bdf87a89857f2d633be3f4b13cbc70
cf319abfba8fc1b33de4c6a6f99e21864cc72563
4fd36e634e762ff2f94e9d66f24ceabe164f9e26
d0364113a1b57ed5017dbea6126b0cc5a5c2886d
9b3d7bf551d20acae4ee943a86c3cf898b6280ba
b35351d566efdde005747503c7f121d49e864848
57b1dc2b20f2e67c3313f0c6127b05041d125fb4
fadcdf4c98e9167f8f06a45dafa08d3acce7a741
3bcfcb7717bfc0e50c5b8f5c7beaed9f3ddf5478
b8388b7b5973dd3e84902c25c5378f9a412d6147
814f07ea363eb0464380ccfce7b4cf5209f1dcb2
b33315c8551bede3fb867efb3fdb1134cdff5115
c7bade1e7cc239e8fceb2c0b06f880e60eb8ebec
bb193f4f0f5b1b8bdc9cc72967f8fa6387faf7c4
b727e8d9f4a4987cbad41c75c630cfdb445c37a0
a2103d7fe328871d8231f8e07ba5dc9182f637b3
e36d269d16660db5bba028746564b5699721def5
35f9c486cc26bdff903241f4ab2b1dac2536059f
cd5314af7e8e120bceda896a3c17daa8eeedd528
200e09df8f0f7b94eb8941136482cf7c60fffb0d
17a618f241a6236c93af5ba2e09238369fc7d784
15aeb2bb0401d428cb7058e1d6554e20369ed352
40b0a406cc23467af8bb63d9a62378fa871e2031
7abd7f4cb237ef33b9e019f4529b6fb05b84284f
ac614b7506e820457417c3ea15ba99fbc8146155
8afd5a714da3f45389e0e4edeb64f49576c57c76
77d10571047d8b4153180e7a89d5c9aae6a84060
35479ce1706725f73bfe99428c43e8fe2e3f9157
360a0864ece712571d3df95e86251d6883bcdf7d
b5cd910848f592e33efb6de3226c07ae545a2aad
f5a9c28ca029ec5d1c5d3c594afa09374adf04e5
b9fce5928a1c5056f66706b67c01cd564e6c0a90
e5a2250e35706127304cd5ed86b81575f2636b5b
f30cffe4cef93aa190bcb1caf407ca0767107d06
45535f6e0af6785676531c81b4a2a3c480a98e70
740bd201b23beded9ade92a93301cddc67c4d106
70460e9e601171276dd6844cd6addd8db5eb2465
44dff2c35acb4736b183cef9e46155386f579716
46ea31f673bc9365fcca558f15c862ef6a899018
34556caf76c2422a76be3d1cecd223fcf435d93c
fea67ed9483b5cc76dc55eb4dd6f52baf445394d
31b1897ece6222826f379c1aebda891384b4b63a
80dcf3713b85b78979d4eb443fce9e992675b5c0
11993c742658321c0c5c200f48231583216d636c
7b5a089ed3007252e61df0aee3fc17c14d051745
890881c9a552c22f4be01dee16ee902c88f6700f
401ba79da09dded82a73996c8e0609a87cbd728b
e06313f41971de730085dcddf640a4549fc54fc3
054d52e86a954a615ed1f5add7f9d6842737d965
d8a60982c456a9cae3de745a37dc3f5985814f7f
2b39f575a510cf581aa828df494e633cc76fafa6
e11d353191175b329b3c9f9af7fa33e3ef9f837d
32ac2659ce98765aaae9c10cc7216d1f1faf155e
5f7f801227868c7abcce7e58dee3eff855011955
a013eaf0fe38d8689e27278bddd4ebf87ac5476b
401b3f3d2d96fa785c5321bb64c97cfb17c509e3
1fa4fd4321fa708b3db5cfb514e2192b00672aff
77976b24ff839c59c3b20d80cb28351ccb5e59a8
09b76d2966e2370a78ed37a31c2f7c23d08609c3
7000b24511618a21d40b39ee213d397e1d29497d
c2a6adfcd18c0d95dbed6ea62ac9c9a912d18123
6ba3609953d5c46a76ca1d0d3d83018be61454e6
3dff6074fe205e36fae219f277ef87aab097e236
1cdc8437fa6c621d96c4dfa5f6370c8fdb9cbc3d
d471720bc8f7ce7109276b49dd9c76b6163007d9
a67b1bdd027629dfc38601b21dc564272e28712c
20125a6d37d5c1614ffe1de94ca064095968e7f0
2b642751ef86265a1c953186810e118740f8bd2d
e562c1d74e2b6744572184e66a0673e55f9ba0b8
ba9687b5d746dda28d4a19c5c96d0679d7c77b15
f39d7d293c3e342b4f447bb440a9b6f72d2d20cc
95750ad9e700efd15d137963ba0dc443e6c9b6b0
0f76d8445048dc0bfcaf05e30b61b338a08f0e48
1a9a4c61d6a371d9e95eaef44fa2452d17a09d22
912b41aad5983d9735379d322eae8f6d40d8bdca
eea0b559472874ff48c34f16bb805108967e6489
ad4e7ba4032e6b1c047230b3144848dbcf66a127
b6d93107393dee6eebb05376a67f2e4dfcb44311
@@ -0,0 +1,8 @@
e34590b7a2d186b3bb9a1170d02d52b36c791c78
8977833d74f8681aa0d9a5e84b0dd3d81519774d
6f5561530cb3a94e4c86454e84732197325be172
ee419e04a961543444be6db66aef52e6e37936d6
d845de9d438e1a249a0c2fcb778e8ea3b7e06cef
0bba4a6c10060405a94d52533af2f9bdacd4f29c
77711c0722964ead965e0ba2ee9ed4a03cb3d292
501d23cac6dd911511f15d091ee031a15b90ebde
@@ -0,0 +1,11 @@
4c8124ffcf4039d292442eeccabdeca5af5c5017
634396b2f541a9f2d58b00be1a07f0c358b999b3
ab25fd8483882c3bda8a458ad2965d2248654335
e34590b7a2d186b3bb9a1170d02d52b36c791c78
8977833d74f8681aa0d9a5e84b0dd3d81519774d
6f5561530cb3a94e4c86454e84732197325be172
ee419e04a961543444be6db66aef52e6e37936d6
d845de9d438e1a249a0c2fcb778e8ea3b7e06cef
0bba4a6c10060405a94d52533af2f9bdacd4f29c
77711c0722964ead965e0ba2ee9ed4a03cb3d292
501d23cac6dd911511f15d091ee031a15b90ebde
@@ -0,0 +1,7 @@
commit 4c8124ffcf4039d292442eeccabdeca5af5c5017
tree 672eca9b7f9e09c22dcb128c283e8c3c8d7697a4
parent 634396b2f541a9f2d58b00be1a07f0c358b999b3
author Tom Preston-Werner <tom@mojombo.com> 1191999972 -0700
committer Tom Preston-Werner <tom@mojombo.com> 1191999972 -0700
implement Grit#heads
@@ -0,0 +1 @@
80f136f

Some files were not shown because too many files have changed in this diff Show More