diff options
Diffstat (limited to 'lib/git')
-rw-r--r-- | lib/git/cmd.py | 57 | ||||
-rw-r--r-- | lib/git/objects/base.py | 24 | ||||
-rw-r--r-- | lib/git/objects/utils.py | 18 | ||||
-rw-r--r-- | lib/git/remote.py | 66 | ||||
-rw-r--r-- | lib/git/repo.py | 25 |
5 files changed, 147 insertions, 43 deletions
diff --git a/lib/git/cmd.py b/lib/git/cmd.py index d04a2bd0..88d6008a 100644 --- a/lib/git/cmd.py +++ b/lib/git/cmd.py @@ -13,7 +13,7 @@ from errors import GitCommandError GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False) execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output', - 'with_exceptions', 'with_raw_output', 'as_process', + 'with_exceptions', 'as_process', 'output_stream' ) extra = {} @@ -105,7 +105,6 @@ class Git(object): with_keep_cwd=False, with_extended_output=False, with_exceptions=True, - with_raw_output=False, as_process=False, output_stream=None ): @@ -132,27 +131,33 @@ class Git(object): ``with_exceptions`` Whether to raise an exception when git returns a non-zero status. - ``with_raw_output`` - Whether to avoid stripping off trailing whitespace. - ``as_process`` Whether to return the created process instance directly from which - streams can be read on demand. This will render with_extended_output, - with_exceptions and with_raw_output ineffective - the caller will have + streams can be read on demand. This will render with_extended_output and + with_exceptions ineffective - the caller will have to deal with the details himself. It is important to note that the process will be placed into an AutoInterrupt wrapper that will interrupt the process once it goes out of scope. If you use the command in iterators, you should pass the whole process instance instead of a single stream. + ``output_stream`` If set to a file-like object, data produced by the git command will be - output to the given stream directly. - Otherwise a new file will be opened. + output to the given stream directly. + This feature only has any effect if as_process is False. Processes will + always be created with a pipe due to issues with subprocess. + This merely is a workaround as data will be copied from the + output pipe to the given output stream directly. + Returns:: - str(output) # extended_output = False (Default) + str(output) # extended_output = False (Default) tuple(int(status), str(stdout), str(stderr)) # extended_output = True + + if ouput_stream is True, the stdout value will be your output stream: + output_stream # extended_output = False + tuple(int(status), output_stream, str(stderr))# extended_output = True Raise GitCommandError @@ -170,37 +175,39 @@ class Git(object): else: cwd=self.git_dir - ostream = subprocess.PIPE - if output_stream is not None: - ostream = output_stream - # Start the process proc = subprocess.Popen(command, cwd=cwd, stdin=istream, stderr=subprocess.PIPE, - stdout=ostream, + stdout=subprocess.PIPE, **extra ) - if as_process: return self.AutoInterrupt(proc) # Wait for the process to return status = 0 try: - stdout_value = proc.stdout.read() + if output_stream is None: + stdout_value = proc.stdout.read() + else: + max_chunk_size = 1024*64 + while True: + chunk = proc.stdout.read(max_chunk_size) + output_stream.write(chunk) + if len(chunk) < max_chunk_size: + break + # END reading output stream + stdout_value = output_stream + # END stdout handling stderr_value = proc.stderr.read() + # waiting here should do nothing as we have finished stream reading status = proc.wait() finally: proc.stdout.close() proc.stderr.close() - # Strip off trailing whitespace by default - if not with_raw_output: - stdout_value = stdout_value.rstrip() - stderr_value = stderr_value.rstrip() - if with_exceptions and status != 0: raise GitCommandError(command, status, stderr_value) @@ -261,7 +268,9 @@ class Git(object): such as in 'ls_files' to call 'ls-files'. ``args`` - is the list of arguments + is the list of arguments. If None is included, it will be pruned. + This allows your commands to call git more conveniently as None + is realized as non-existent ``kwargs`` is a dict of keyword arguments. @@ -287,7 +296,7 @@ class Git(object): # Prepare the argument list opt_args = self.transform_kwargs(**kwargs) - ext_args = self.__unpack_args(args) + ext_args = self.__unpack_args([a for a in args if a is not None]) args = opt_args + ext_args call = ["git", dashify(method)] diff --git a/lib/git/objects/base.py b/lib/git/objects/base.py index ab1da7b0..0dfd1a23 100644 --- a/lib/git/objects/base.py +++ b/lib/git/objects/base.py @@ -16,6 +16,9 @@ class Object(LazyMixin): This Object also serves as a constructor for instances of the correct type:: inst = Object.new(repo,id) + inst.id # objects sha in hex + inst.size # objects uncompressed data size + inst.data # byte string containing the whole data of the object """ TYPES = ("blob", "tree", "commit", "tag") __slots__ = ("repo", "id", "size", "data" ) @@ -115,6 +118,27 @@ class Object(LazyMixin): """ return '<git.%s "%s">' % (self.__class__.__name__, self.id) + @property + def data_stream(self): + """ + Returns + File Object compatible stream to the uncompressed raw data of the object + """ + proc = self.repo.git.cat_file(self.type, self.id, as_process=True) + return utils.ProcessStreamAdapter(proc, "stdout") + + def stream_data(self, ostream): + """ + Writes our data directly to the given output stream + + ``ostream`` + File object compatible stream object. + + Returns + self + """ + self.repo.git.cat_file(self.type, self.id, output_stream=ostream) + return self class IndexObject(Object): """ diff --git a/lib/git/objects/utils.py b/lib/git/objects/utils.py index 367ed2b7..7bb4e8e2 100644 --- a/lib/git/objects/utils.py +++ b/lib/git/objects/utils.py @@ -52,3 +52,21 @@ def parse_actor_and_date(line): m = _re_actor_epoch.search(line) actor, epoch = m.groups() return (Actor._from_string(actor), int(epoch)) + + + +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) diff --git a/lib/git/remote.py b/lib/git/remote.py index 6a9c0efb..7febf2ee 100644 --- a/lib/git/remote.py +++ b/lib/git/remote.py @@ -7,7 +7,7 @@ Module implementing a remote object allowing easy access to git remotes """ -from git.utils import LazyMixin, Iterable +from git.utils import LazyMixin, Iterable, IterableList from refs import RemoteReference class _SectionConstraint(object): @@ -121,7 +121,7 @@ class Remote(LazyMixin, Iterable): Returns List of RemoteRef objects """ - out_refs = list() + out_refs = IterableList(RemoteReference._id_attribute_) for ref in RemoteReference.list_items(self.repo): if ref.remote_name == self.name: out_refs.append(ref) @@ -185,7 +185,9 @@ class Remote(LazyMixin, Iterable): def update(self, **kwargs): """ - Fetch all changes for this remote, including new branches + 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 ). ``kwargs`` Additional arguments passed to git-remote update @@ -196,6 +198,64 @@ class Remote(LazyMixin, Iterable): self.repo.git.remote("update", self.name) return self + def fetch(self, refspec=None, **kwargs): + """ + Fetch the latest changes for this remote + + ``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 + + ``**kwargs`` + Additional arguments to be passed to git-fetch + + Returns + self + """ + self.repo.git.fetch(self, refspec, **kwargs) + return self + + def pull(self, refspec=None, **kwargs): + """ + Pull changes from the given branch, being the same as a fetch followed + by a merge of branch with your local branch. + + ``refspec`` + see 'fetch' method + + ``**kwargs`` + Additional arguments to be passed to git-pull + + Returns + self + """ + self.repo.git.pull(self, refspec, **kwargs) + return self + + def push(self, refspec=None, **kwargs): + """ + Push changes from source branch in refspec to target branch in refspec. + + ``refspec`` + see 'fetch' method + + ``**kwargs`` + Additional arguments to be passed to git-push + + Returns + self + """ + self.repo.git.push(self, refspec, **kwargs) + return self + @property def config_reader(self): """ diff --git a/lib/git/repo.py b/lib/git/repo.py index 37847c98..b6624d8b 100644 --- a/lib/git/repo.py +++ b/lib/git/repo.py @@ -19,7 +19,7 @@ from config import GitConfigParser from remote import Remote def touch(filename): - fp = open(filename, "w") + fp = open(filename, "a") fp.close() def is_git_dir(d): @@ -432,11 +432,13 @@ class Repo(object): # start from the one which is fastest to evaluate default_args = ('--abbrev=40', '--full-index', '--raw') if index: + # diff index against HEAD if len(self.git.diff('HEAD', '--cached', *default_args)): return True # END index handling if working_tree: - if len(self.git.diff('HEAD', *default_args)): + # diff index against working tree + if len(self.git.diff(*default_args)): return True # END working tree handling if untracked_files: @@ -639,32 +641,23 @@ class Repo(object): Examples:: - >>> repo.archive(open("archive" + >>> repo.archive(open("archive")) <String containing tar.gz archive> - >>> repo.archive_tar_gz('a87ff14') - <String containing tar.gz archive for commit a87ff14> - - >>> repo.archive_tar_gz('master', 'myproject/') - <String containing tar.gz archive and prefixed with 'myproject/'> - Raise GitCommandError in case something went wrong + Returns + self """ if treeish is None: treeish = self.active_branch if prefix and 'prefix' not in kwargs: kwargs['prefix'] = prefix - kwargs['as_process'] = True kwargs['output_stream'] = ostream - proc = self.git.archive(treeish, **kwargs) - status = proc.wait() - if status != 0: - raise GitCommandError( "git-archive", status, proc.stderr.read() ) - - + self.git.archive(treeish, **kwargs) + return self def __repr__(self): return '<git.Repo "%s">' % self.path |