Forráskód Böngészése

Add git thaw/freeze to depot_tools.

R=agable@chromium.org, hinoka@chromium.org, stip@chromium.org, szager@chromium.org
BUG=261738

Review URL: https://codereview.chromium.org/181043018

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@256778 0039d316-1c4b-4281-b951-d872f2087c98
iannucci@chromium.org 11 éve
szülő
commit
97345ebf1a
6 módosított fájl, 235 hozzáadás és 8 törlés
  1. 11 0
      git-freeze
  2. 13 0
      git-thaw
  3. 27 7
      git_common.py
  4. 73 0
      git_freezer.py
  5. 12 1
      tests/git_common_test.py
  6. 99 0
      tests/git_freezer_test.py

+ 11 - 0
git-freeze

@@ -0,0 +1,11 @@
+#!/bin/bash
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# git_freezer.py freeze -- a git-command to suspend all existing working
+# directory modifications. This can be reversed with the 'git thaw' command.
+
+SCRIPT=git_freezer.py
+set -- freeze "$@"
+. $(type -P python_git_runner.sh)

+ 13 - 0
git-thaw

@@ -0,0 +1,13 @@
+#!/bin/bash
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# git_freezer.py thaw -- a git-command to reverse the effect of the 'git freeze'
+# command. Any changes suspended on this branch with the freeze command will be
+# restored to the state they were in immediately prior to running the freeze
+# command.
+
+SCRIPT=git_freezer.py
+set -- thaw "$@"
+. $(type -P python_git_runner.sh)

+ 27 - 7
git_common.py

@@ -19,6 +19,7 @@ import binascii
 import contextlib
 import contextlib
 import functools
 import functools
 import logging
 import logging
+import os
 import signal
 import signal
 import sys
 import sys
 import tempfile
 import tempfile
@@ -139,7 +140,7 @@ def ScopedPool(*args, **kwargs):
 
 
 class ProgressPrinter(object):
 class ProgressPrinter(object):
   """Threaded single-stat status message printer."""
   """Threaded single-stat status message printer."""
-  def __init__(self, fmt, enabled=None, stream=sys.stderr, period=0.5):
+  def __init__(self, fmt, enabled=None, fout=sys.stderr, period=0.5):
     """Create a ProgressPrinter.
     """Create a ProgressPrinter.
 
 
     Use it as a context manager which produces a simple 'increment' method:
     Use it as a context manager which produces a simple 'increment' method:
@@ -155,7 +156,7 @@ class ProgressPrinter(object):
         should go.
         should go.
       enabled (bool) - If this is None, will default to True if
       enabled (bool) - If this is None, will default to True if
         logging.getLogger() is set to INFO or more verbose.
         logging.getLogger() is set to INFO or more verbose.
-      stream (file-like) - The stream to print status messages to.
+      fout (file-like) - The stream to print status messages to.
       period (float) - The time in seconds for the printer thread to wait
       period (float) - The time in seconds for the printer thread to wait
         between printing.
         between printing.
     """
     """
@@ -168,7 +169,7 @@ class ProgressPrinter(object):
     self._count = 0
     self._count = 0
     self._dead = False
     self._dead = False
     self._dead_cond = threading.Condition()
     self._dead_cond = threading.Condition()
-    self._stream = stream
+    self._stream = fout
     self._thread = threading.Thread(target=self._run)
     self._thread = threading.Thread(target=self._run)
     self._period = period
     self._period = period
 
 
@@ -239,17 +240,36 @@ def run(*cmd, **kwargs):
 
 
   kwargs
   kwargs
     autostrip (bool) - Strip the output. Defaults to True.
     autostrip (bool) - Strip the output. Defaults to True.
-  Output string is always strip()'d.
   """
   """
   autostrip = kwargs.pop('autostrip', True)
   autostrip = kwargs.pop('autostrip', True)
-  cmd = (GIT_EXE,) + cmd
-  logging.debug('Running %s', ' '.join(repr(tok) for tok in cmd))
-  ret = subprocess2.check_output(cmd, stderr=subprocess2.PIPE, **kwargs)
+
+  retstream, proc = stream_proc(*cmd, **kwargs)
+  ret = retstream.read()
+  retcode = proc.wait()
+  if retcode != 0:
+    raise subprocess2.CalledProcessError(retcode, cmd, os.getcwd(), ret, None)
+
   if autostrip:
   if autostrip:
     ret = (ret or '').strip()
     ret = (ret or '').strip()
   return ret
   return ret
 
 
 
 
+def stream_proc(*cmd, **kwargs):
+  """Runs a git command. Returns stdout as a file.
+
+  If logging is DEBUG, we'll print the command before we run it.
+  """
+  cmd = (GIT_EXE,) + cmd
+  logging.debug('Running %s', ' '.join(repr(tok) for tok in cmd))
+  proc = subprocess2.Popen(cmd, stderr=subprocess2.VOID,
+                           stdout=subprocess2.PIPE, **kwargs)
+  return proc.stdout, proc
+
+
+def stream(*cmd, **kwargs):
+  return stream_proc(*cmd, **kwargs)[0]
+
+
 def hash_one(reflike):
 def hash_one(reflike):
   return run('rev-parse', reflike)
   return run('rev-parse', reflike)
 
 

+ 73 - 0
git_freezer.py

@@ -0,0 +1,73 @@
+#!/usr/local/bin/python
+import sys
+import re
+import optparse
+
+import subcommand
+import subprocess2
+
+from git_common import run, stream
+
+FREEZE = 'FREEZE'
+SECTIONS = {
+  'indexed': 'soft',
+  'unindexed': 'mixed'
+}
+MATCHER = re.compile(r'%s.(%s)' % (FREEZE, '|'.join(SECTIONS)))
+
+
+def freeze():
+  took_action = False
+
+  try:
+    run('commit', '-m', FREEZE + '.indexed')
+    took_action = True
+  except subprocess2.CalledProcessError:
+    pass
+
+  try:
+    run('add', '-A')
+    run('commit', '-m', FREEZE + '.unindexed')
+    took_action = True
+  except subprocess2.CalledProcessError:
+    pass
+
+  if not took_action:
+    return 'Nothing to freeze.'
+
+
+def thaw():
+  took_action = False
+  for sha in (s.strip() for s in stream('rev-list', 'HEAD').xreadlines()):
+    msg = run('show', '--format=%f%b', '-s', 'HEAD')
+    match = MATCHER.match(msg)
+    if not match:
+      if not took_action:
+        return 'Nothing to thaw.'
+      break
+
+    run('reset', '--' + SECTIONS[match.group(1)], sha)
+    took_action = True
+
+
+def CMDfreeze(parser, args):  # pragma: no cover
+  """Freeze a branch's changes."""
+  parser.parse_args(args)
+  return freeze()
+
+
+def CMDthaw(parser, args):  # pragma: no cover
+  """Returns a frozen branch to the state before it was frozen."""
+  parser.parse_args(args)
+  return thaw()
+
+
+def main():  # pragma: no cover
+  dispatcher = subcommand.CommandDispatcher(__name__)
+  ret = dispatcher.execute(optparse.OptionParser(), sys.argv[1:])
+  if ret:
+    print ret
+
+
+if __name__ == '__main__':  # pragma: no cover
+  main()

+ 12 - 1
tests/git_common_test.py

@@ -128,7 +128,7 @@ class ProgressPrinterTest(GitCommonTestBase):
     fmt = '%(count)d/10'
     fmt = '%(count)d/10'
     stream = self.FakeStream()
     stream = self.FakeStream()
 
 
-    pp = self.gc.ProgressPrinter(fmt, enabled=True, stream=stream, period=0.01)
+    pp = self.gc.ProgressPrinter(fmt, enabled=True, fout=stream, period=0.01)
     with pp as inc:
     with pp as inc:
       for _ in xrange(10):
       for _ in xrange(10):
         time.sleep(0.02)
         time.sleep(0.02)
@@ -190,6 +190,17 @@ class GitReadOnlyFunctionsTest(git_test_utils.GitRepoReadOnlyTestBase,
       self.repo['D']
       self.repo['D']
     )
     )
 
 
+  def testStream(self):
+    items = set(self.repo.commit_map.itervalues())
+
+    def testfn():
+      for line in self.gc.stream('log', '--format=%H').xreadlines():
+        line = line.strip()
+        self.assertIn(line, items)
+        items.remove(line)
+
+    self.repo.run(testfn)
+
   def testCurrentBranch(self):
   def testCurrentBranch(self):
     self.repo.git('checkout', 'branch_D')
     self.repo.git('checkout', 'branch_D')
     self.assertEqual(self.repo.run(self.gc.current_branch), 'branch_D')
     self.assertEqual(self.repo.run(self.gc.current_branch), 'branch_D')

+ 99 - 0
tests/git_freezer_test.py

@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Unit tests for git_freezer.py"""
+
+import os
+import sys
+
+DEPOT_TOOLS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+sys.path.insert(0, DEPOT_TOOLS_ROOT)
+
+from testing_support import coverage_utils
+from testing_support import git_test_utils
+
+
+class GitFreezeThaw(git_test_utils.GitRepoReadWriteTestBase):
+  @classmethod
+  def setUpClass(cls):
+    super(GitFreezeThaw, cls).setUpClass()
+    import git_freezer
+    cls.gf = git_freezer
+
+  REPO = """
+  A B C D
+    B E D
+  """
+
+  COMMIT_A = {
+    'some/files/file1': {'data': 'file1'},
+    'some/files/file2': {'data': 'file2'},
+    'some/files/file3': {'data': 'file3'},
+    'some/other/file':  {'data': 'otherfile'},
+  }
+
+  COMMIT_C = {
+    'some/files/file2': {
+      'mode': 0755,
+      'data': 'file2 - vanilla'},
+  }
+
+  COMMIT_E = {
+    'some/files/file2': {'data': 'file2 - merged'},
+  }
+
+  COMMIT_D = {
+    'some/files/file2': {'data': 'file2 - vanilla\nfile2 - merged'},
+  }
+
+  def testNothing(self):
+    self.assertIsNotNone(self.repo.run(self.gf.thaw))  # 'Nothing to thaw'
+    self.assertIsNotNone(self.repo.run(self.gf.freeze))  # 'Nothing to freeze'
+
+  def testAll(self):
+    def inner():
+      with open('some/files/file2', 'a') as f2:
+        print >> f2, 'cool appended line'
+      os.mkdir('some/other_files')
+      with open('some/other_files/subdir_file', 'w') as f3:
+        print >> f3, 'new file!'
+      with open('some/files/file5', 'w') as f5:
+        print >> f5, 'New file!1!one!'
+
+      STATUS_1 = '\n'.join((
+        ' M some/files/file2',
+        'A  some/files/file5',
+        '?? some/other_files/'
+      )) + '\n'
+
+      self.repo.git('add', 'some/files/file5')
+
+      # Freeze group 1
+      self.assertEquals(self.repo.git('status', '--porcelain').stdout, STATUS_1)
+      self.assertIsNone(self.gf.freeze())
+      self.assertEquals(self.repo.git('status', '--porcelain').stdout, '')
+
+      # Freeze group 2
+      with open('some/files/file2', 'a') as f2:
+        print >> f2, 'new! appended line!'
+      self.assertEquals(self.repo.git('status', '--porcelain').stdout,
+                        ' M some/files/file2\n')
+      self.assertIsNone(self.gf.freeze())
+      self.assertEquals(self.repo.git('status', '--porcelain').stdout, '')
+
+      # Thaw it out!
+      self.assertIsNone(self.gf.thaw())
+      self.assertIsNotNone(self.gf.thaw())  # One thaw should thaw everything
+
+      self.assertEquals(self.repo.git('status', '--porcelain').stdout, STATUS_1)
+
+    self.repo.run(inner)
+
+
+
+if __name__ == '__main__':
+  sys.exit(coverage_utils.covered_main(
+    os.path.join(DEPOT_TOOLS_ROOT, 'git_freezer.py')
+  ))