138 lines
4.0 KiB
Python
138 lines
4.0 KiB
Python
# stash.py
|
|
# Copyright (C) 2018 Jelmer Vernooij <jelmer@samba.org>
|
|
#
|
|
# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
|
|
# General Public License as public by the Free Software Foundation; version 2.0
|
|
# or (at your option) any later version. You can redistribute it and/or
|
|
# modify it under the terms of either of these two licenses.
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
# You should have received a copy of the licenses; if not, see
|
|
# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
|
|
# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
|
|
# License, Version 2.0.
|
|
#
|
|
|
|
"""Stash handling."""
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import os
|
|
|
|
from dulwich.file import GitFile
|
|
from dulwich.index import (
|
|
commit_tree,
|
|
iter_fresh_objects,
|
|
)
|
|
from dulwich.reflog import drop_reflog_entry, read_reflog
|
|
|
|
|
|
DEFAULT_STASH_REF = b"refs/stash"
|
|
|
|
|
|
class Stash(object):
|
|
"""A Git stash.
|
|
|
|
Note that this doesn't currently update the working tree.
|
|
"""
|
|
|
|
def __init__(self, repo, ref=DEFAULT_STASH_REF):
|
|
self._ref = ref
|
|
self._repo = repo
|
|
|
|
@property
|
|
def _reflog_path(self):
|
|
return os.path.join(
|
|
self._repo.commondir(), "logs", os.fsdecode(self._ref)
|
|
)
|
|
|
|
def stashes(self):
|
|
try:
|
|
with GitFile(self._reflog_path, "rb") as f:
|
|
return reversed(list(read_reflog(f)))
|
|
except FileNotFoundError:
|
|
return []
|
|
|
|
@classmethod
|
|
def from_repo(cls, repo):
|
|
"""Create a new stash from a Repo object."""
|
|
return cls(repo)
|
|
|
|
def drop(self, index):
|
|
"""Drop entry with specified index."""
|
|
with open(self._reflog_path, "rb+") as f:
|
|
drop_reflog_entry(f, index, rewrite=True)
|
|
if len(self) == 0:
|
|
os.remove(self._reflog_path)
|
|
del self._repo.refs[self._ref]
|
|
return
|
|
if index == 0:
|
|
self._repo.refs[self._ref] = self[0].new_sha
|
|
|
|
def pop(self, index):
|
|
raise NotImplementedError(self.pop)
|
|
|
|
def push(self, committer=None, author=None, message=None):
|
|
"""Create a new stash.
|
|
|
|
Args:
|
|
committer: Optional committer name to use
|
|
author: Optional author name to use
|
|
message: Optional commit message
|
|
"""
|
|
# First, create the index commit.
|
|
commit_kwargs = {}
|
|
if committer is not None:
|
|
commit_kwargs["committer"] = committer
|
|
if author is not None:
|
|
commit_kwargs["author"] = author
|
|
|
|
index = self._repo.open_index()
|
|
index_tree_id = index.commit(self._repo.object_store)
|
|
index_commit_id = self._repo.do_commit(
|
|
ref=None,
|
|
tree=index_tree_id,
|
|
message=b"Index stash",
|
|
merge_heads=[self._repo.head()],
|
|
no_verify=True,
|
|
**commit_kwargs
|
|
)
|
|
|
|
# Then, the working tree one.
|
|
stash_tree_id = commit_tree(
|
|
self._repo.object_store,
|
|
iter_fresh_objects(
|
|
index,
|
|
os.fsencode(self._repo.path),
|
|
object_store=self._repo.object_store,
|
|
),
|
|
)
|
|
|
|
if message is None:
|
|
message = b"A stash on " + self._repo.head()
|
|
|
|
# TODO(user): Just pass parents into do_commit()?
|
|
self._repo.refs[self._ref] = self._repo.head()
|
|
|
|
cid = self._repo.do_commit(
|
|
ref=self._ref,
|
|
tree=stash_tree_id,
|
|
message=message,
|
|
merge_heads=[index_commit_id],
|
|
no_verify=True,
|
|
**commit_kwargs
|
|
)
|
|
|
|
return cid
|
|
|
|
def __getitem__(self, index):
|
|
return list(self.stashes())[index]
|
|
|
|
def __len__(self):
|
|
return len(list(self.stashes()))
|