summaryrefslogtreecommitdiff
path: root/git/remote.py
diff options
context:
space:
mode:
Diffstat (limited to 'git/remote.py')
-rw-r--r--git/remote.py178
1 files changed, 89 insertions, 89 deletions
diff --git a/git/remote.py b/git/remote.py
index 75e88e43..226ee959 100644
--- a/git/remote.py
+++ b/git/remote.py
@@ -25,9 +25,9 @@ from refs import (
)
from git.util import (
- join_path,
- finalize_process
- )
+ join_path,
+ finalize_process
+ )
from gitdb.util import join
import re
@@ -41,7 +41,7 @@ __all__ = ('RemoteProgress', 'PushInfo', 'FetchInfo', 'Remote')
def digest_process_messages(fh, progress):
"""Read progress messages from file-like object fh, supplying the respective
progress messages to the progress instance.
-
+
:param fh: File handle to read from
:return: list(line, ...) list of lines without linebreaks that did
not contain progress information"""
@@ -76,11 +76,11 @@ def add_progress(kwargs, git, progress):
#} END utilities
-
+
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
@@ -93,14 +93,14 @@ class PushInfo(object):
info.summary # summary line providing human readable english text about the push
"""
__slots__ = ('local_ref', 'remote_ref_string', 'flags', 'old_commit', '_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 """
@@ -110,7 +110,7 @@ class PushInfo(object):
self._remote = remote
self.old_commit = old_commit
self.summary = summary
-
+
@property
def remote_ref(self):
"""
@@ -126,28 +126,28 @@ class PushInfo(object):
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"""
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('['):
@@ -174,14 +174,14 @@ class PushInfo(object):
# have to use constructor here as the sha usually is abbreviated
old_commit = remote.repo.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
@@ -193,16 +193,16 @@ class FetchInfo(object):
# field is set to the previous location of ref, otherwise None
"""
__slots__ = ('ref','old_commit', 'flags', 'note')
-
+
NEW_TAG, NEW_HEAD, HEAD_UPTODATE, TAG_UPDATE, REJECTED, FORCED_UPDATE, \
FAST_FORWARD, ERROR = [ 1 << x for x in range(8) ]
-
+
# %c %-*s %-*s -> %s (%s)
re_fetch_result = re.compile("^\s*(.) (\[?[\w\s\.]+\]?)\s+(.+) -> ([/\w_\+\.-]+)( \(.*\)?$)?")
-
+
_flag_map = { '!' : ERROR, '+' : FORCED_UPDATE, '-' : TAG_UPDATE, '*' : 0,
'=' : HEAD_UPTODATE, ' ' : FAST_FORWARD }
-
+
def __init__(self, ref, flags, note = '', old_commit = None):
"""
Initialize a new instance
@@ -211,28 +211,28 @@ class FetchInfo(object):
self.flags = flags
self.note = note
self.old_commit = old_commit
-
+
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
@@ -240,13 +240,13 @@ class FetchInfo(object):
* 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:
@@ -254,7 +254,7 @@ class FetchInfo(object):
ref_type_name, fetch_note = fetch_note.split(' ', 1)
except ValueError: # unpack error
raise ValueError("Failed to parse FETCH__HEAD line: %r" % fetch_line)
-
+
# 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
@@ -271,7 +271,7 @@ class FetchInfo(object):
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")
@@ -296,14 +296,14 @@ class FetchInfo(object):
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 ''
-
+
# parse flags from control_character
flags = 0
try:
@@ -311,7 +311,7 @@ class FetchInfo(object):
except KeyError:
raise ValueError("Control character %r unknown as parsed from line %r" % (control_character, line))
# END control char exception hanlding
-
+
# parse operation string for more info - makes no sense for symbolic refs
old_commit = None
if isinstance(remote_local_ref, Reference):
@@ -328,30 +328,30 @@ class FetchInfo(object):
old_commit = repo.rev_parse(operation.split(split_token)[0])
# END handle refspec
# END reference flag handling
-
+
return cls(remote_local_ref, flags, note, old_commit)
-
+
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 os.name == 'nt':
# 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__
@@ -361,13 +361,13 @@ class Remote(LazyMixin, Iterable):
# 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:
@@ -375,32 +375,32 @@ class Remote(LazyMixin, Iterable):
except 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":
self._config_reader = SectionConstraint(self.repo.config_reader(), 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)
-
+
@classmethod
def iter_items(cls, repo):
""":return: Iterator yielding Remote objects of the given repository"""
@@ -413,7 +413,7 @@ class Remote(LazyMixin, Iterable):
raise ValueError("Remote-Section has invalid format: %r" % section)
yield Remote(repo, section[lbound+1:rbound])
# END for each configuration section
-
+
@property
def refs(self):
"""
@@ -425,7 +425,7 @@ class Remote(LazyMixin, Iterable):
out_refs.extend(RemoteReference.list_items(self.repo, remote=self.name))
assert out_refs, "Remote %s did not have any references" % self.name
return out_refs
-
+
@property
def stale_refs(self):
"""
@@ -433,7 +433,7 @@ class Remote(LazyMixin, Iterable):
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."""
out_refs = IterableList(RemoteReference._id_attribute_, "%s/" % self.name)
@@ -447,7 +447,7 @@ class Remote(LazyMixin, Iterable):
out_refs.append(RemoteReference(self.repo, fqhn))
# END for each line
return out_refs
-
+
@classmethod
def create(cls, repo, name, url, **kwargs):
"""Create a new remote to the given repository
@@ -456,30 +456,30 @@ class Remote(LazyMixin, Iterable):
: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"""
repo.git.remote( "add", name, 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"""
repo.git.remote("rm", 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
try:
@@ -488,19 +488,19 @@ class Remote(LazyMixin, Iterable):
pass
#END handle exception
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 """
self.repo.git.remote("update", self.name)
return self
-
+
def _get_fetch_info_from_stderr(self, proc, progress):
# skip first line as it is some remote info we are not interested in
output = IterableList('name')
@@ -522,30 +522,30 @@ class Remote(LazyMixin, Iterable):
# END handle special messages
fetch_info_lines.append(line)
# END for each line
-
+
# read head information
fp = open(join(self.repo.git_dir, 'FETCH_HEAD'),'r')
fetch_head_info = fp.readlines()
fp.close()
-
+
# NOTE: HACK Just disabling this line will make github repositories work much better.
# I simply couldn't stand it anymore, so here is the quick and dirty fix ... .
# This project needs a lot of work !
# assert len(fetch_info_lines) == len(fetch_head_info), "len(%s) != len(%s)" % (fetch_head_info, fetch_info_lines)
-
+
output.extend(FetchInfo._from_line(self.repo, err_line, fetch_line)
for err_line,fetch_line in zip(fetch_info_lines, fetch_head_info))
-
+
finalize_process(proc)
return output
-
+
def _get_push_info(self, proc, 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
digest_process_messages(proc.stderr, progress)
-
+
output = IterableList('name')
for line in proc.stdout.readlines():
try:
@@ -555,14 +555,14 @@ class Remote(LazyMixin, Iterable):
pass
# END exception handling
# END for each line
-
+
finalize_process(proc)
return output
-
-
+
+
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
@@ -572,7 +572,7 @@ class Remote(LazyMixin, Iterable):
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
@@ -583,7 +583,7 @@ class Remote(LazyMixin, Iterable):
: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."""
@@ -594,11 +594,11 @@ class Remote(LazyMixin, Iterable):
args = [refspec]
proc = self.repo.git.fetch(self, *args, with_extended_output=True, as_process=True, v=True, **kwargs)
return self._get_fetch_info_from_stderr(proc, progress or RemoteProgress())
-
+
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
@@ -606,16 +606,16 @@ class Remote(LazyMixin, Iterable):
kwargs = add_progress(kwargs, self.repo.git, progress)
proc = self.repo.git.pull(self, refspec, with_extended_output=True, as_process=True, v=True, **kwargs)
return self._get_fetch_info_from_stderr(proc, progress or RemoteProgress())
-
+
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:
Instance of type RemoteProgress allowing the caller to receive
progress information until the method returns.
If None, progress information will be discarded
-
+
:param kwargs: Additional arguments to be passed to git-push
:return:
IterableList(PushInfo, ...) iterable list of PushInfo instances, each
@@ -628,7 +628,7 @@ class Remote(LazyMixin, Iterable):
kwargs = add_progress(kwargs, self.repo.git, progress)
proc = self.repo.git.push(self, refspec, porcelain=True, as_process=True, **kwargs)
return self._get_push_info(proc, progress or RemoteProgress())
-
+
@property
def config_reader(self):
"""
@@ -636,7 +636,7 @@ class Remote(LazyMixin, Iterable):
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
-
+
@property
def config_writer(self):
"""
@@ -644,12 +644,12 @@ class Remote(LazyMixin, Iterable):
:note:
You can only own one writer at a time - delete it to release the
configuration file and make it useable 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
try:
del(self._config_reader)