浏览代码

Merge basic stash support.

Jelmer Vernooij 6 年之前
父节点
当前提交
f0c9245672
共有 6 个文件被更改,包括 218 次插入0 次删除
  1. 2 0
      NEWS
  2. 37 0
      bin/dulwich
  3. 24 0
      dulwich/porcelain.py
  4. 119 0
      dulwich/stash.py
  5. 1 0
      dulwich/tests/__init__.py
  6. 35 0
      dulwich/tests/test_stash.py

+ 2 - 0
NEWS

@@ -13,6 +13,8 @@
 
   * Add `Repo.get_shallow` method. (Jelmer Vernooij)
 
+  * Add basic `dulwich.stash` module. (Jelmer Vernooij)
+
  BUG FIXES
 
   * Fix handling of encoding for tags. (Jelmer Vernooij, #608)

+ 37 - 0
bin/dulwich

@@ -569,6 +569,42 @@ class cmd_check_mailmap(Command):
             print(canonical_identity)
 
 
+class cmd_stash_list(Command):
+
+    def run(self, args):
+        parser = optparse.OptionParser()
+        options, args = parser.parse_args(args)
+        for i, entry in porcelain.stash_list('.'):
+            print("stash@{%d}: %s" % (i, entry.message.rstrip('\n')))
+
+
+class cmd_stash_push(Command):
+
+    def run(self, args):
+        parser = optparse.OptionParser()
+        options, args = parser.parse_args(args)
+        porcelain.stash_push('.')
+        print("Saved working directory and index state")
+
+
+class cmd_stash_pop(Command):
+
+    def run(self, args):
+        parser = optparse.OptionParser()
+        options, args = parser.parse_args(args)
+        porcelain.stash_pop('.')
+        print("Restrored working directory and index state")
+
+
+class cmd_stash(SuperCommand):
+
+    subcommands = {
+        "list": cmd_stash_list,
+        "pop": cmd_stash_pop,
+        "push": cmd_stash_push,
+    }
+
+
 class cmd_help(Command):
 
     def run(self, args):
@@ -622,6 +658,7 @@ commands = {
     "rev-list": cmd_rev_list,
     "rm": cmd_rm,
     "show": cmd_show,
+    "stash": cmd_stash,
     "status": cmd_status,
     "symbolic-ref": cmd_symbolic_ref,
     "tag": cmd_tag,

+ 24 - 0
dulwich/porcelain.py

@@ -1258,3 +1258,27 @@ def fsck(repo):
                 o.check()
             except Exception as e:
                 yield (sha, e)
+
+
+def stash_list(repo):
+    """List all stashes in a repository."""
+    with open_repo_closing(repo) as r:
+        from dulwich.stash import Stash
+        stash = Stash.from_repo(r)
+        return enumerate(list(stash.stashes()))
+
+
+def stash_push(repo):
+    """Push a new stash onto the stack."""
+    with open_repo_closing(repo) as r:
+        from dulwich.stash import Stash
+        stash = Stash.from_repo(r)
+        stash.push()
+
+
+def stash_pop(repo):
+    """Pop a new stash from the stack."""
+    with open_repo_closing(repo) as r:
+        from dulwich.stash import Stash
+        stash = Stash.from_repo(r)
+        stash.pop()

+ 119 - 0
dulwich/stash.py

@@ -0,0 +1,119 @@
+# 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 errno
+import os
+
+from dulwich.file import GitFile
+from dulwich.index import (
+    commit_tree,
+    iter_fresh_objects,
+    )
+from dulwich.reflog import 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
+
+    def stashes(self):
+        reflog_path = os.path.join(
+            self._repo.commondir(), 'logs', self._ref)
+        try:
+            with GitFile(reflog_path, 'rb') as f:
+                return reversed(list(read_reflog(f)))
+        except EnvironmentError as e:
+            if e.errno == errno.ENOENT:
+                return []
+            raise
+
+    @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."""
+        raise NotImplementedError(self.drop)
+
+    def pop(self, index):
+        raise NotImplementedError(self.drop)
+
+    def push(self, committer=None, author=None, message=None):
+        """Create a new stash.
+
+        :param committer: Optional committer name to use
+        :param author: Optional author name to use
+        :param 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()],
+            **commit_kwargs)
+
+        # Then, the working tree one.
+        stash_tree_id = commit_tree(
+                self._repo.object_store,
+                iter_fresh_objects(
+                    index, self._repo.path,
+                    object_store=self._repo.object_store))
+
+        if message is None:
+            message = b"A stash on " + self._repo.head()
+
+        # TODO(jelmer): 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],
+            **commit_kwargs)
+
+        return cid
+
+    def __getitem__(self, index):
+        return self._stashes()[index]
+
+    def __len__(self):
+        return len(self._stashes())

+ 1 - 0
dulwich/tests/__init__.py

@@ -124,6 +124,7 @@ def self_test_suite():
         'refs',
         'repository',
         'server',
+        'stash',
         'utils',
         'walk',
         'web',

+ 35 - 0
dulwich/tests/test_stash.py

@@ -0,0 +1,35 @@
+# test_stash.py -- tests for stash
+# Copyright (C) 2018 Jelmer Vernooij <jelmer@jelmer.uk>
+#
+# 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.
+#
+
+"""Tests for stashes."""
+
+from . import TestCase
+
+from ..repo import MemoryRepo
+from ..stash import Stash
+
+
+class StashTests(TestCase):
+    """Tests for stash."""
+
+    def test_obtain(self):
+        repo = MemoryRepo()
+        stash = Stash.from_repo(repo)
+        self.assertIsInstance(stash, Stash)