diff --git a/cms/djangoapps/contentstore/git_export_utils.py b/cms/djangoapps/contentstore/git_export_utils.py
new file mode 100644
index 0000000000..e94b10d94c
--- /dev/null
+++ b/cms/djangoapps/contentstore/git_export_utils.py
@@ -0,0 +1,185 @@
+"""
+Utilities for export a course's XML into a git repository,
+committing and pushing the changes.
+"""
+
+import logging
+import os
+import subprocess
+from urlparse import urlparse
+
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.utils import timezone
+from django.utils.translation import ugettext_lazy as _
+
+from xmodule.contentstore.django import contentstore
+from xmodule.course_module import CourseDescriptor
+from xmodule.modulestore.django import modulestore
+from xmodule.modulestore.xml_exporter import export_to_xml
+
+log = logging.getLogger(__name__)
+
+GIT_REPO_EXPORT_DIR = getattr(settings, 'GIT_REPO_EXPORT_DIR', None)
+GIT_EXPORT_DEFAULT_IDENT = getattr(settings, 'GIT_EXPORT_DEFAULT_IDENT',
+ {'name': 'STUDIO_EXPORT_TO_GIT',
+ 'email': 'STUDIO_EXPORT_TO_GIT@example.com'})
+
+
+class GitExportError(Exception):
+ """
+ Convenience exception class for git export error conditions.
+ """
+
+ NO_EXPORT_DIR = _("GIT_REPO_EXPORT_DIR not set or path {0} doesn't exist, "
+ "please create it, or configure a different path with "
+ "GIT_REPO_EXPORT_DIR".format(GIT_REPO_EXPORT_DIR))
+ URL_BAD = _('Non writable git url provided. Expecting something like:'
+ ' git@github.com:mitocw/edx4edx_lite.git')
+ URL_NO_AUTH = _('If using http urls, you must provide the username '
+ 'and password in the url. Similar to '
+ 'https://user:pass@github.com/user/course.')
+ DETACHED_HEAD = _('Unable to determine branch, repo in detached HEAD mode')
+ CANNOT_PULL = _('Unable to update or clone git repository.')
+ XML_EXPORT_FAIL = _('Unable to export course to xml.')
+ CONFIG_ERROR = _('Unable to configure git username and password')
+ CANNOT_COMMIT = _('Unable to commit changes. This is usually '
+ 'because there are no changes to be committed')
+ CANNOT_PUSH = _('Unable to push changes. This is usually '
+ 'because the remote repository cannot be contacted')
+ BAD_COURSE = _('Bad course location provided')
+ MISSING_BRANCH = _('Missing branch on fresh clone')
+
+
+def cmd_log(cmd, cwd):
+ """
+ Helper function to redirect stderr to stdout and log the command
+ used along with the output. Will raise subprocess.CalledProcessError if
+ command doesn't return 0, and returns the command's output.
+ """
+ output = subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT)
+ log.debug(_('Command was: {0!r}. '
+ 'Working directory was: {1!r}'.format(' '.join(cmd), cwd)))
+ log.debug(_('Command output was: {0!r}'.format(output)))
+ return output
+
+
+def export_to_git(course_loc, repo, user='', rdir=None):
+ """Export a course to git."""
+ # pylint: disable=R0915
+
+ if course_loc.startswith('i4x://'):
+ course_loc = course_loc[6:]
+
+ if not GIT_REPO_EXPORT_DIR:
+ raise GitExportError(GitExportError.NO_EXPORT_DIR)
+
+ if not os.path.isdir(GIT_REPO_EXPORT_DIR):
+ raise GitExportError(GitExportError.NO_EXPORT_DIR)
+
+ # Check for valid writable git url
+ if not (repo.endswith('.git') or
+ repo.startswith(('http:', 'https:', 'file:'))):
+ raise GitExportError(GitExportError.URL_BAD)
+
+ # Check for username and password if using http[s]
+ if repo.startswith('http:') or repo.startswith('https:'):
+ parsed = urlparse(repo)
+ if parsed.username is None or parsed.password is None:
+ raise GitExportError(GitExportError.URL_NO_AUTH)
+ if rdir:
+ rdir = os.path.basename(rdir)
+ else:
+ rdir = repo.rsplit('/', 1)[-1].rsplit('.git', 1)[0]
+
+ log.debug("rdir = %s", rdir)
+
+ # Pull or clone repo before exporting to xml
+ # and update url in case origin changed.
+ rdirp = '{0}/{1}'.format(GIT_REPO_EXPORT_DIR, rdir)
+ branch = None
+ if os.path.exists(rdirp):
+ log.info(_('Directory already exists, doing a git reset and pull '
+ 'instead of git clone.'))
+ cwd = rdirp
+ # Get current branch
+ cmd = ['git', 'symbolic-ref', '--short', 'HEAD']
+ try:
+ branch = cmd_log(cmd, cwd).strip('\n')
+ except subprocess.CalledProcessError as ex:
+ log.exception('Failed to get branch: %r', ex.output)
+ raise GitExportError(GitExportError.DETACHED_HEAD)
+
+ cmds = [
+ ['git', 'remote', 'set-url', 'origin', repo],
+ ['git', 'fetch', 'origin'],
+ ['git', 'reset', '--hard', 'origin/{0}'.format(branch)],
+ ['git', 'pull'],
+ ]
+ else:
+ cmds = [['git', 'clone', repo]]
+ cwd = GIT_REPO_EXPORT_DIR
+
+ cwd = os.path.abspath(cwd)
+ for cmd in cmds:
+ try:
+ cmd_log(cmd, cwd)
+ except subprocess.CalledProcessError as ex:
+ log.exception('Failed to pull git repository: %r', ex.output)
+ raise GitExportError(GitExportError.CANNOT_PULL)
+
+ # export course as xml before commiting and pushing
+ try:
+ location = CourseDescriptor.id_to_location(course_loc)
+ except ValueError:
+ raise GitExportError(GitExportError.BAD_COURSE)
+
+ root_dir = os.path.dirname(rdirp)
+ course_dir = os.path.splitext(os.path.basename(rdirp))[0]
+ try:
+ export_to_xml(modulestore('direct'), contentstore(), location,
+ root_dir, course_dir, modulestore())
+ except (EnvironmentError, AttributeError):
+ log.exception('Failed export to xml')
+ raise GitExportError(GitExportError.XML_EXPORT_FAIL)
+
+ # Get current branch if not already set
+ if not branch:
+ cmd = ['git', 'symbolic-ref', '--short', 'HEAD']
+ try:
+ branch = cmd_log(cmd, os.path.abspath(rdirp)).strip('\n')
+ except subprocess.CalledProcessError as ex:
+ log.exception('Failed to get branch from freshly cloned repo: %r',
+ ex.output)
+ raise GitExportError(GitExportError.MISSING_BRANCH)
+
+ # Now that we have fresh xml exported, set identity, add
+ # everything to git, commit, and push to the right branch.
+ ident = {}
+ try:
+ user = User.objects.get(username=user)
+ ident['name'] = user.username
+ ident['email'] = user.email
+ except User.DoesNotExist:
+ # That's ok, just use default ident
+ ident = GIT_EXPORT_DEFAULT_IDENT
+ time_stamp = timezone.now()
+ cwd = os.path.abspath(rdirp)
+ commit_msg = 'Export from Studio at {1}'.format(user, time_stamp)
+ try:
+ cmd_log(['git', 'config', 'user.email', ident['email']], cwd)
+ cmd_log(['git', 'config', 'user.name', ident['name']], cwd)
+ except subprocess.CalledProcessError as ex:
+ log.exception('Error running git configure commands: %r', ex.output)
+ raise GitExportError(GitExportError.CONFIG_ERROR)
+ try:
+ cmd_log(['git', 'add', '.'], cwd)
+ cmd_log(['git', 'commit', '-a', '-m', commit_msg], cwd)
+ except subprocess.CalledProcessError as ex:
+ log.exception('Unable to commit changes: %r', ex.output)
+ raise GitExportError(GitExportError.CANNOT_COMMIT)
+ try:
+ cmd_log(['git', 'push', '-q', 'origin', branch], cwd)
+ except subprocess.CalledProcessError as ex:
+ log.exception('Error running git push command: %r', ex.output)
+ raise GitExportError(GitExportError.CANNOT_PUSH)
diff --git a/cms/djangoapps/contentstore/management/commands/git_export.py b/cms/djangoapps/contentstore/management/commands/git_export.py
index 47ac1d7427..848ef832e7 100644
--- a/cms/djangoapps/contentstore/management/commands/git_export.py
+++ b/cms/djangoapps/contentstore/management/commands/git_export.py
@@ -10,178 +10,19 @@ repository before attempting to export the XML, add, and commit changes if
any have taken place.
This functionality is also available as an export view in studio if the giturl
-attribute is set and the FEATURE['ENABLE_PUSH_TO_LMS'] is set.
+attribute is set and the FEATURE['ENABLE_EXPORT_GIT'] is set.
"""
import logging
from optparse import make_option
-import os
-import subprocess
-from urlparse import urlparse
-from django.conf import settings
-from django.contrib.auth.models import User
from django.core.management.base import BaseCommand, CommandError
-from django.utils import timezone
from django.utils.translation import ugettext as _
-from xmodule.contentstore.django import contentstore
-from xmodule.course_module import CourseDescriptor
-from xmodule.modulestore.django import modulestore
-from xmodule.modulestore.xml_exporter import export_to_xml
+import contentstore.git_export_utils as git_export_utils
log = logging.getLogger(__name__)
-GIT_REPO_EXPORT_DIR = getattr(settings, 'GIT_REPO_EXPORT_DIR',
- '/edx/var/edxapp/export_course_repos')
-GIT_EXPORT_DEFAULT_IDENT = getattr(settings, 'GIT_EXPORT_DEFAULT_IDENT',
- {'name': 'STUDIO_PUSH_TO_LMS',
- 'email': 'STUDIO_PUSH_TO_LMS@example.com'})
-
-
-class GitExportError(Exception):
- """
- Convenience exception class for git export error conditions.
- """
-
- NO_EXPORT_DIR = _("Path {0} doesn't exist, please create it, "
- "or configure a different path with "
- "GIT_REPO_EXPORT_DIR").format(GIT_REPO_EXPORT_DIR)
- URL_BAD = _('Non writable git url provided. Expecting something like:'
- ' git@github.com:mitocw/edx4edx_lite.git')
- URL_NO_AUTH = _('If using http urls, you must provide the username '
- 'and password in the url. Similar to '
- 'https://user:pass@github.com/user/course.')
- DETACHED_HEAD = _('Unable to determine branch, repo in detached HEAD mode')
- CANNOT_PULL = _('Unable to update or clone git repository.')
- XML_EXPORT_FAIL = _('Unable to export course to xml.')
- CANNOT_COMMIT = _('Unable to commit or push changes.')
- BAD_COURSE = _('Bad course location provided')
- MISSING_BRANCH = _('Missing branch on fresh clone')
-
-
-def cmd_log(cmd, cwd):
- """
- Helper function to redirect stderr to stdout and log the command
- used along with the output. Will raise subprocess.CalledProcessError if
- command doesn't return 0, and returns the command's output.
- """
- output = subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT)
- log.debug(_('Command was: {0!r}. '
- 'Working directory was: {1!r}').format(' '.join(cmd), cwd))
- log.debug(_('Command output was: {0!r}'.format(output)))
- return output
-
-
-def export_to_git(course_loc, repo, user='', rdir=None):
- """Export a course to git."""
- # pylint: disable=R0915
-
- if course_loc.startswith('i4x://'):
- course_loc = course_loc[6:]
-
- if not os.path.isdir(GIT_REPO_EXPORT_DIR):
- raise GitExportError(GitExportError.NO_EXPORT_DIR)
-
- # Check for valid writable git url
- if not (repo.endswith('.git') or
- repo.startswith(('http:', 'https:', 'file:'))):
- raise GitExportError(GitExportError.URL_BAD)
-
- # Check for username and password if using http[s]
- if repo.startswith('http:') or repo.startswith('https:'):
- parsed = urlparse(repo)
- if parsed.username is None or parsed.password is None:
- raise GitExportError(GitExportError.URL_NO_AUTH)
- if rdir:
- rdir = os.path.basename(rdir)
- else:
- rdir = repo.rsplit('/', 1)[-1].rsplit('.git', 1)[0]
-
- log.debug("rdir = %s", rdir)
-
- # Pull or clone repo before exporting to xml
- # and update url in case origin changed.
- rdirp = '{0}/{1}'.format(GIT_REPO_EXPORT_DIR, rdir)
- branch = None
- if os.path.exists(rdirp):
- log.info(_('Directory already exists, doing a git reset and pull '
- 'instead of git clone.'))
- cwd = rdirp
- # Get current branch
- cmd = ['git', 'symbolic-ref', '--short', 'HEAD', ]
- try:
- branch = cmd_log(cmd, cwd).strip('\n')
- except subprocess.CalledProcessError as ex:
- log.exception('Failed to get branch: %r', ex.output)
- raise GitExportError(GitExportError.DETACHED_HEAD)
-
- cmds = [
- ['git', 'remote', 'set-url', 'origin', repo, ],
- ['git', 'fetch', 'origin', ],
- ['git', 'reset', '--hard', 'origin/{0}'.format(branch), ],
- ['git', 'pull', ],
- ]
- else:
- cmds = [['git', 'clone', repo, ], ]
- cwd = GIT_REPO_EXPORT_DIR
-
- cwd = os.path.abspath(cwd)
- for cmd in cmds:
- try:
- cmd_log(cmd, cwd)
- except subprocess.CalledProcessError as ex:
- log.exception('Failed to pull git repository: %r', ex.output)
- raise GitExportError(GitExportError.CANNOT_PULL)
-
- # export course as xml before commiting and pushing
- try:
- location = CourseDescriptor.id_to_location(course_loc)
- except ValueError:
- raise GitExportError(GitExportError.BAD_COURSE)
-
- root_dir = os.path.dirname(rdirp)
- course_dir = os.path.splitext(os.path.basename(rdirp))[0]
- try:
- export_to_xml(modulestore('direct'), contentstore(), location,
- root_dir, course_dir, modulestore())
- except (EnvironmentError, AttributeError):
- log.exception('Failed export to xml')
- raise GitExportError(GitExportError.XML_EXPORT_FAIL)
-
- # Get current branch if not already set
- if not branch:
- cmd = ['git', 'symbolic-ref', '--short', 'HEAD', ]
- try:
- branch = cmd_log(cmd, os.path.abspath(rdirp)).strip('\n')
- except subprocess.CalledProcessError as ex:
- log.exception('Failed to get branch from freshly cloned repo: %r',
- ex.output)
- raise GitExportError(GitExportError.MISSING_BRANCH)
-
- # Now that we have fresh xml exported, set identity, add
- # everything to git, commit, and push to the right branch.
- ident = {}
- try:
- user = User.objects.get(username=user)
- ident['name'] = user.username
- ident['email'] = user.email
- except User.DoesNotExist:
- # That's ok, just use default ident
- ident = GIT_EXPORT_DEFAULT_IDENT
- time_stamp = timezone.now()
- cwd = os.path.abspath(rdirp)
- commit_msg = 'Export from Studio at {1}'.format(user, time_stamp)
- try:
- cmd_log(['git', 'config', 'user.email', ident['email'], ], cwd)
- cmd_log(['git', 'config', 'user.name', ident['name'], ], cwd)
- cmd_log(['git', 'add', '.'], cwd)
- cmd_log(['git', 'commit', '-a', '-m', commit_msg], cwd)
- cmd_log(['git', 'push', '-q', 'origin', branch], cwd)
- except subprocess.CalledProcessError as ex:
- log.exception('Error running git push commands: %r', ex.output)
- raise GitExportError(GitExportError.CANNOT_COMMIT)
-
class Command(BaseCommand):
"""
@@ -189,8 +30,9 @@ class Command(BaseCommand):
"""
option_list = BaseCommand.option_list + (
- make_option('--user', '-u', dest='user',
- help='Add a user to the commit message.'),
+ make_option('--username', '-u', dest='user',
+ help=('Specify a username from LMS/Studio to be used '
+ 'as the commit author.')),
make_option('--repo_dir', '-r', dest='repo',
help='Specify existing git repo directory.'),
)
@@ -206,16 +48,16 @@ class Command(BaseCommand):
"""
if len(args) != 2:
- raise CommandError(_('This script requires exactly two arguments: '
- 'course_loc and git_url'))
+ raise CommandError('This script requires exactly two arguments: '
+ 'course_loc and git_url')
# Rethrow GitExportError as CommandError for SystemExit
try:
- export_to_git(
+ git_export_utils.export_to_git(
args[0],
args[1],
options.get('user', ''),
options.get('rdir', None)
)
- except GitExportError as ex:
+ except git_export_utils.GitExportError as ex:
raise CommandError(str(ex))
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_git_export.py b/cms/djangoapps/contentstore/management/commands/tests/test_git_export.py
index 6d62dcddfc..228571035a 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_git_export.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_git_export.py
@@ -16,17 +16,17 @@ from django.core.management.base import CommandError
from django.test.utils import override_settings
from contentstore.tests.utils import CourseTestCase
-import contentstore.management.commands.git_export as git_export
-from contentstore.management.commands.git_export import GitExportError
+import contentstore.git_export_utils as git_export_utils
+from contentstore.git_export_utils import GitExportError
-FEATURES_WITH_PUSH_TO_LMS = settings.FEATURES.copy()
-FEATURES_WITH_PUSH_TO_LMS['ENABLE_PUSH_TO_LMS'] = True
+FEATURES_WITH_EXPORT_GIT = settings.FEATURES.copy()
+FEATURES_WITH_EXPORT_GIT['ENABLE_EXPORT_GIT'] = True
TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex
@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE)
-@override_settings(FEATURES=FEATURES_WITH_PUSH_TO_LMS)
+@override_settings(FEATURES=FEATURES_WITH_EXPORT_GIT)
class TestGitExport(CourseTestCase):
"""
Excercise the git_export django management command with various inputs.
@@ -38,16 +38,16 @@ class TestGitExport(CourseTestCase):
"""
super(TestGitExport, self).setUp()
- if not os.path.isdir(git_export.GIT_REPO_EXPORT_DIR):
- os.mkdir(git_export.GIT_REPO_EXPORT_DIR)
- self.addCleanup(shutil.rmtree, git_export.GIT_REPO_EXPORT_DIR)
+ if not os.path.isdir(git_export_utils.GIT_REPO_EXPORT_DIR):
+ os.mkdir(git_export_utils.GIT_REPO_EXPORT_DIR)
+ self.addCleanup(shutil.rmtree, git_export_utils.GIT_REPO_EXPORT_DIR)
self.bare_repo_dir = '{0}/data/test_bare.git'.format(
os.path.abspath(settings.TEST_ROOT))
if not os.path.isdir(self.bare_repo_dir):
os.mkdir(self.bare_repo_dir)
self.addCleanup(shutil.rmtree, self.bare_repo_dir)
- subprocess.check_output(['git', '--bare', 'init', ],
+ subprocess.check_output(['git', '--bare', 'init'],
cwd=self.bare_repo_dir)
def test_command(self):
@@ -56,64 +56,62 @@ class TestGitExport(CourseTestCase):
test output.
"""
with self.assertRaises(SystemExit) as ex:
- self.assertRaisesRegexp(
- CommandError, 'This script requires.*',
+ with self.assertRaisesRegexp(CommandError, 'This script requires.*'):
call_command('git_export', 'blah', 'blah', 'blah',
- stderr=StringIO.StringIO()))
+ stderr=StringIO.StringIO())
self.assertEqual(ex.exception.code, 1)
with self.assertRaises(SystemExit) as ex:
- self.assertRaisesRegexp(CommandError, 'This script requires.*',
- call_command('git_export',
- stderr=StringIO.StringIO()))
+ with self.assertRaisesRegexp(CommandError, 'This script requires.*'):
+ call_command('git_export', stderr=StringIO.StringIO())
self.assertEqual(ex.exception.code, 1)
# Send bad url to get course not exported
with self.assertRaises(SystemExit) as ex:
- self.assertRaisesRegexp(CommandError, GitExportError.URL_BAD,
- call_command('git_export', 'foo', 'silly',
- stderr=StringIO.StringIO()))
+ with self.assertRaisesRegexp(CommandError, GitExportError.URL_BAD):
+ call_command('git_export', 'foo', 'silly',
+ stderr=StringIO.StringIO())
self.assertEqual(ex.exception.code, 1)
def test_bad_git_url(self):
"""
Test several bad URLs for validation
"""
- with self.assertRaisesRegexp(GitExportError, GitExportError.URL_BAD):
- git_export.export_to_git('', 'Sillyness')
+ with self.assertRaisesRegexp(GitExportError, str(GitExportError.URL_BAD)):
+ git_export_utils.export_to_git('', 'Sillyness')
- with self.assertRaisesRegexp(GitExportError, GitExportError.URL_BAD):
- git_export.export_to_git('', 'example.com:edx/notreal')
+ with self.assertRaisesRegexp(GitExportError, str(GitExportError.URL_BAD)):
+ git_export_utils.export_to_git('', 'example.com:edx/notreal')
with self.assertRaisesRegexp(GitExportError,
- GitExportError.URL_NO_AUTH):
- git_export.export_to_git('', 'http://blah')
+ str(GitExportError.URL_NO_AUTH)):
+ git_export_utils.export_to_git('', 'http://blah')
def test_bad_git_repos(self):
"""
Test invalid git repos
"""
- test_repo_path = '{}/test_repo'.format(git_export.GIT_REPO_EXPORT_DIR)
+ test_repo_path = '{}/test_repo'.format(git_export_utils.GIT_REPO_EXPORT_DIR)
self.assertFalse(os.path.isdir(test_repo_path))
# Test bad clones
with self.assertRaisesRegexp(GitExportError,
- GitExportError.CANNOT_PULL):
- git_export.export_to_git(
+ str(GitExportError.CANNOT_PULL)):
+ git_export_utils.export_to_git(
'foo/blah/100',
'https://user:blah@example.com/test_repo.git')
self.assertFalse(os.path.isdir(test_repo_path))
# Setup good repo with bad course to test xml export
with self.assertRaisesRegexp(GitExportError,
- GitExportError.XML_EXPORT_FAIL):
- git_export.export_to_git(
+ str(GitExportError.XML_EXPORT_FAIL)):
+ git_export_utils.export_to_git(
'foo/blah/100',
'file://{0}'.format(self.bare_repo_dir))
# Test bad git remote after successful clone
with self.assertRaisesRegexp(GitExportError,
- GitExportError.CANNOT_PULL):
- git_export.export_to_git(
+ str(GitExportError.CANNOT_PULL)):
+ git_export_utils.export_to_git(
'foo/blah/100',
'https://user:blah@example.com/r.git')
@@ -121,8 +119,8 @@ class TestGitExport(CourseTestCase):
"""
Test valid git url, but bad course.
"""
- with self.assertRaisesRegexp(GitExportError, GitExportError.BAD_COURSE):
- git_export.export_to_git(
+ with self.assertRaisesRegexp(GitExportError, str(GitExportError.BAD_COURSE)):
+ git_export_utils.export_to_git(
'', 'file://{0}'.format(self.bare_repo_dir), '', '/blah')
@unittest.skipIf(os.environ.get('GIT_CONFIG') or
@@ -138,23 +136,23 @@ class TestGitExport(CourseTestCase):
Test skipped if git global config override environment variable GIT_CONFIG
is set.
"""
- git_export.export_to_git(
+ git_export_utils.export_to_git(
self.course.id,
'file://{0}'.format(self.bare_repo_dir),
'enigma'
)
expect_string = '{0}|{1}\n'.format(
- git_export.GIT_EXPORT_DEFAULT_IDENT['name'],
- git_export.GIT_EXPORT_DEFAULT_IDENT['email']
+ git_export_utils.GIT_EXPORT_DEFAULT_IDENT['name'],
+ git_export_utils.GIT_EXPORT_DEFAULT_IDENT['email']
)
- cwd = os.path.abspath(git_export.GIT_REPO_EXPORT_DIR / 'test_bare')
+ cwd = os.path.abspath(git_export_utils.GIT_REPO_EXPORT_DIR / 'test_bare')
git_log = subprocess.check_output(['git', 'log', '-1',
- '--format=%an|%ae', ], cwd=cwd)
+ '--format=%an|%ae'], cwd=cwd)
self.assertEqual(expect_string, git_log)
# Make changes to course so there is something commit
self.populateCourse()
- git_export.export_to_git(
+ git_export_utils.export_to_git(
self.course.id,
'file://{0}'.format(self.bare_repo_dir),
self.user.username
@@ -164,19 +162,19 @@ class TestGitExport(CourseTestCase):
self.user.email,
)
git_log = subprocess.check_output(
- ['git', 'log', '-1', '--format=%an|%ae', ], cwd=cwd)
+ ['git', 'log', '-1', '--format=%an|%ae'], cwd=cwd)
self.assertEqual(expect_string, git_log)
def test_no_change(self):
"""
Test response if there are no changes
"""
- git_export.export_to_git(
+ git_export_utils.export_to_git(
'i4x://{0}'.format(self.course.id),
'file://{0}'.format(self.bare_repo_dir)
)
with self.assertRaisesRegexp(GitExportError,
- GitExportError.CANNOT_COMMIT):
- git_export.export_to_git(
+ str(GitExportError.CANNOT_COMMIT)):
+ git_export_utils.export_to_git(
self.course.id, 'file://{0}'.format(self.bare_repo_dir))
diff --git a/cms/djangoapps/contentstore/tests/test_push_to_lms.py b/cms/djangoapps/contentstore/tests/test_export_git.py
similarity index 70%
rename from cms/djangoapps/contentstore/tests/test_push_to_lms.py
rename to cms/djangoapps/contentstore/tests/test_export_git.py
index fc7c04c5ce..6288a7ef00 100644
--- a/cms/djangoapps/contentstore/tests/test_push_to_lms.py
+++ b/cms/djangoapps/contentstore/tests/test_export_git.py
@@ -11,10 +11,11 @@ from uuid import uuid4
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test.utils import override_settings
-from django.utils.translation import ugettext as _
+from pymongo import MongoClient
from .utils import CourseTestCase
-import contentstore.management.commands.git_export as git_export
+import contentstore.git_export_utils as git_export_utils
+from xmodule.contentstore.django import _CONTENTSTORE
from xmodule.modulestore.django import modulestore
TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
@@ -22,7 +23,7 @@ TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().
@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE)
-class TestPushToLMS(CourseTestCase):
+class TestExportGit(CourseTestCase):
"""
Tests pushing a course to a git repository
"""
@@ -31,14 +32,18 @@ class TestPushToLMS(CourseTestCase):
"""
Setup test course, user, and url.
"""
- super(TestPushToLMS, self).setUp()
+ super(TestExportGit, self).setUp()
self.course_module = modulestore().get_item(self.course.location)
- self.test_url = reverse('push_to_lms', kwargs={
+ self.test_url = reverse('export_git', kwargs={
'org': self.course.location.org,
'course': self.course.location.course,
'name': self.course.location.name,
})
+ def tearDown(self):
+ MongoClient().drop_database(TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'])
+ _CONTENTSTORE.clear()
+
def test_giturl_missing(self):
"""
Test to make sure an appropriate error is displayed
@@ -47,20 +52,20 @@ class TestPushToLMS(CourseTestCase):
response = self.client.get(self.test_url)
self.assertEqual(200, response.status_code)
self.assertIn(
- _('giturl must be defined in your '
- 'course settings before you can push to LMS.'),
+ ('giturl must be defined in your '
+ 'course settings before you can export to git.'),
response.content
)
response = self.client.get('{}?action=push'.format(self.test_url))
self.assertEqual(200, response.status_code)
self.assertIn(
- _('giturl must be defined in your '
- 'course settings before you can push to LMS.'),
+ ('giturl must be defined in your '
+ 'course settings before you can export to git.'),
response.content
)
- def test_course_import_failures(self):
+ def test_course_export_failures(self):
"""
Test failed course export response.
"""
@@ -68,19 +73,19 @@ class TestPushToLMS(CourseTestCase):
modulestore().save_xmodule(self.course_module)
response = self.client.get('{}?action=push'.format(self.test_url))
- self.assertIn(_('Export Failed:'), response.content)
+ self.assertIn('Export Failed:', response.content)
- def test_course_import_success(self):
+ def test_course_export_success(self):
"""
Test successful course export response.
"""
# Build out local bare repo, and set course git url to it
- repo_dir = os.path.abspath(git_export.GIT_REPO_EXPORT_DIR)
+ repo_dir = os.path.abspath(git_export_utils.GIT_REPO_EXPORT_DIR)
os.mkdir(repo_dir)
self.addCleanup(shutil.rmtree, repo_dir)
bare_repo_dir = '{0}/test_repo.git'.format(
- os.path.abspath(git_export.GIT_REPO_EXPORT_DIR))
+ os.path.abspath(git_export_utils.GIT_REPO_EXPORT_DIR))
os.mkdir(bare_repo_dir)
self.addCleanup(shutil.rmtree, bare_repo_dir)
@@ -91,4 +96,4 @@ class TestPushToLMS(CourseTestCase):
modulestore().save_xmodule(self.course_module)
response = self.client.get('{}?action=push'.format(self.test_url))
- self.assertIn(_('Export Succeeded'), response.content)
+ self.assertIn('Export Succeeded', response.content)
diff --git a/cms/djangoapps/contentstore/views/__init__.py b/cms/djangoapps/contentstore/views/__init__.py
index 7458a883c8..5e644468fd 100644
--- a/cms/djangoapps/contentstore/views/__init__.py
+++ b/cms/djangoapps/contentstore/views/__init__.py
@@ -14,7 +14,7 @@ from .item import *
from .import_export import *
from .preview import *
from .public import *
-from .push_to_lms import *
+from .export_git import *
from .user import *
from .tabs import *
from .transcripts_ajax import *
diff --git a/cms/djangoapps/contentstore/views/push_to_lms.py b/cms/djangoapps/contentstore/views/export_git.py
similarity index 72%
rename from cms/djangoapps/contentstore/views/push_to_lms.py
rename to cms/djangoapps/contentstore/views/export_git.py
index 5094145275..e3ec9f4f60 100644
--- a/cms/djangoapps/contentstore/views/push_to_lms.py
+++ b/cms/djangoapps/contentstore/views/export_git.py
@@ -10,44 +10,44 @@ from django.core.exceptions import PermissionDenied
from django_future.csrf import ensure_csrf_cookie
from django.utils.translation import ugettext as _
-from .access import has_access
+from .access import has_course_access
+import contentstore.git_export_utils as git_export_utils
from edxmako.shortcuts import render_to_response
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
-import contentstore.management.commands.git_export as git_export
log = logging.getLogger(__name__)
@ensure_csrf_cookie
@login_required
-def push_to_lms(request, org, course, name):
+def export_git(request, org, course, name):
"""
- This method serves up the 'Push to LMS' page
+ This method serves up the 'Export to Git' page
"""
location = Location('i4x', org, course, 'course', name)
- if not has_access(request.user, location):
+ if not has_course_access(request.user, location):
raise PermissionDenied()
course_module = modulestore().get_item(location)
failed = False
- log.debug('push_to_lms course_module=%s', course_module)
+ log.debug('export_git course_module=%s', course_module)
msg = ""
if 'action' in request.GET and course_module.giturl:
if request.GET['action'] == 'push':
try:
- git_export.export_to_git(
+ git_export_utils.export_to_git(
course_module.id,
course_module.giturl,
request.user,
)
msg = _('Course successfully exported to git repository')
- except git_export.GitExportError as ex:
+ except git_export_utils.GitExportError as ex:
failed = True
msg = str(ex)
- return render_to_response('push_to_lms.html', {
+ return render_to_response('export_git.html', {
'context_course': course_module,
'msg': msg,
'failed': failed,
diff --git a/cms/envs/test.py b/cms/envs/test.py
index 6aca0f7ccb..5edbc89686 100644
--- a/cms/envs/test.py
+++ b/cms/envs/test.py
@@ -38,7 +38,7 @@ GITHUB_REPO_ROOT = TEST_ROOT / "data"
COMMON_TEST_DATA_ROOT = COMMON_ROOT / "test" / "data"
# For testing "push to lms"
-FEATURES['ENABLE_PUSH_TO_LMS'] = True
+FEATURES['ENABLE_EXPORT_GIT'] = True
GIT_REPO_EXPORT_DIR = TEST_ROOT / "export_course_repos"
# Makes the tests run much faster...
diff --git a/cms/static/sass/style-app-extend1.scss b/cms/static/sass/style-app-extend1.scss
index 3cb6245037..2702157ee6 100644
--- a/cms/static/sass/style-app-extend1.scss
+++ b/cms/static/sass/style-app-extend1.scss
@@ -44,7 +44,7 @@
@import 'views/users';
@import 'views/checklists';
@import 'views/textbooks';
-@import 'views/push';
+@import 'views/export-git';
// base - contexts
@import 'contexts/ie'; // ie-specific rules (mostly for known/older bugs)
diff --git a/cms/static/sass/views/_push.scss b/cms/static/sass/views/_export-git.scss
similarity index 84%
rename from cms/static/sass/views/_push.scss
rename to cms/static/sass/views/_export-git.scss
index 743d53a586..dda371b45e 100644
--- a/cms/static/sass/views/_push.scss
+++ b/cms/static/sass/views/_export-git.scss
@@ -1,7 +1,7 @@
-// studio - views - push to lms
+// studio - views - export to git
// ====================
-.view-push {
+.view-export-git {
// UI: basic layout
.content-primary, .content-supplementary {
@@ -24,24 +24,24 @@
h3 {
font-size: 19px;
- font-weight: 700;
+ font-weight: 700;
}
- .push-info-block {
+ .export-git-info-block {
dt {
font-size: 19px;
font-weight: 700;
- margin-top: 12px;
+ margin-top: 12px;
}
dd {
font-size: 17px;
- margin-bottom: 20px;
+ margin-bottom: 20px;
}
-
+
.course_text {
- color: $green;
+ color: $green;
}
.giturl_text {
color: $blue;
@@ -57,7 +57,7 @@
}
// UI: export controls
- .push-controls {
+ .export-git-controls {
@include box-sizing(border-box);
@extend %ui-window;
padding: $baseline ($baseline*1.5) ($baseline*1.5) ($baseline*1.5);
@@ -66,7 +66,7 @@
@extend %t-title4;
}
- .action-push {
+ .action-export-git {
@extend %btn-primary-blue;
@extend %t-action1;
display: block;
@@ -87,7 +87,6 @@
display: inline-block;
vertical-align: middle;
}
- }
+ }
}
}
-
diff --git a/cms/templates/push_to_lms.html b/cms/templates/export_git.html
similarity index 54%
rename from cms/templates/push_to_lms.html
rename to cms/templates/export_git.html
index 6a90b80abe..5e54569496 100644
--- a/cms/templates/push_to_lms.html
+++ b/cms/templates/export_git.html
@@ -1,50 +1,52 @@
<%inherit file="base.html" />
<%namespace name='static' file='static_content.html'/>
-<%!
+<%!
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
%>
-<%block name="title">${_("Push Course to LMS")}%block>
-<%block name="bodyclass">is-signedin course tools view-push%block>
+<%block name="title">${_("Export Course to Git")}%block>
+<%block name="bodyclass">is-signedin course tools view-export-git%block>
<%block name="content">
${_("Tools")}
- > ${_("Push to LMS")}
+ > ${_("Export to Git")}
-
+
-
${_("About Push to LMS")}
-
${_("Use this to export your course to its git repository.")}
-
${_("This will then trigger an automatic update of the main LMS site and update the contents of your course visible there to students.")}
+
${_("About Export to Git")}
+
+
${_("Use this to export your course to its git repository.")}
+
${_("This will then trigger an automatic update of the main LMS site and update the contents of your course visible there to students if automatic git imports are configured.")}
+
-
-
${_("Push Course:")}
-
+
+
${_("Export Course to Git:")}
+
% if not context_course.giturl:
-
${_("giturl must be defined in your course settings before you can push to LMS.")}
+
${_("giturl must be defined in your course settings before you can export to git.")}