mirror of
https://github.com/Manuel83/craftbeerpi3
synced 2026-09-19 15:59:07 +02:00
python 3 migration
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
[](https://pypi.python.org/pypi/GitPython/)
|
||||
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 23 23
|
||||
[](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
|
||||
[](https://travis-ci.org/gitpython-developers/GitPython)
|
||||
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 69 69
|
||||
[](https://coveralls.io/r/gitpython-developers/GitPython?branch=master)
|
||||
e40ad6369bc74d01af4dc41d3a9b8e25ac2aa01e 70 70
|
||||
[](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
|
||||
+33
@@ -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)
|
||||
Vendored
+1
@@ -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
|
||||
BIN
Binary file not shown.
@@ -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:"
|
||||
+4
@@ -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
Reference in New Issue
Block a user