Skip to content

Commit 19fdc9d

Browse files
committed
Added --push-remote CLI option.
Adding option so users can specify which remote to push to. This is handy in case the remote hosting HTML files is a bare repo on a web host. Copying all remotes and all fetch/push URLs to the cloned repo when using push().
1 parent c4f6ed8 commit 19fdc9d

File tree

13 files changed

+137
-45
lines changed

13 files changed

+137
-45
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ after_success:
2121
- eval "$(ssh-agent -s)"; touch docs/key; chmod 0600 docs/key
2222
- openssl aes-256-cbc -d -K "$encrypted_9c2bf3fbb9ea_key" -iv "$encrypted_9c2bf3fbb9ea_iv" < docs/key.enc > docs/key
2323
&& ssh-add docs/key
24-
- git remote set-url origin "[email protected]:$TRAVIS_REPO_SLUG"
24+
- git remote set-url --push origin "[email protected]:$TRAVIS_REPO_SLUG"
2525
- export ${!TRAVIS*}
2626
- tox -e docsV
2727

README.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,12 @@ This project adheres to `Semantic Versioning <http://semver.org/>`_.
4747
Unreleased
4848
----------
4949

50+
Added
51+
* Command line option: ``--push-remote``
52+
5053
Fixed
51-
* Carrying over to cloned repo the git remote push URL if it's different from fetch URL.
54+
* Copy all remotes from the original repo to the temporarily cloned repo when pushing built docs to a remote.
55+
Carries over all remote URLs in case user defines a different URL for push vs fetch.
5256

5357
2.1.0 - 2016-08-22
5458
------------------

docs/github_pages.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ section look like this:
130130
&& ssh-add docs/key # Use && to prevent ssh-add from prompting during pull requests.
131131
- git config --global user.email "[email protected]"
132132
- git config --global user.name "Travis CI"
133-
- git remote set-url origin "[email protected]:$TRAVIS_REPO_SLUG"
133+
- git remote set-url --push origin "[email protected]:$TRAVIS_REPO_SLUG"
134134
- export ${!TRAVIS*} # Optional, for commit messages.
135135
- sphinx-versioning push docs gh-pages .
136136

docs/settings.rst

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ Push Arguments
277277
three times in case of race conditions with other processes also trying to push files to the same branch (e.g. multiple
278278
Jenkins/Travis jobs).
279279

280-
HTML files are committed to :option:`DEST_BRANCH` and pushed to origin.
280+
HTML files are committed to :option:`DEST_BRANCH` and pushed to :option:`--push-remote`.
281281

282282
Positional Arguments
283283
--------------------
@@ -286,10 +286,11 @@ In addition to the :ref:`common arguments <common-positional-arguments>`:
286286

287287
.. option:: DEST_BRANCH
288288

289-
The branch name where generated docs will be committed to. The branch will then be pushed to origin. If there is a
290-
race condition with another job pushing to origin the docs will be re-generated and pushed again.
289+
The branch name where generated docs will be committed to. The branch will then be pushed to the remote specified in
290+
:option:`--push-remote`. If there is a race condition with another job pushing to the remote the docs will be
291+
re-generated and pushed again.
291292

292-
This must be a branch and not a tag. This also must already exist in origin.
293+
This must be a branch and not a tag. This also must already exist in the remote.
293294

294295
.. option:: REL_DEST
295296

@@ -320,3 +321,13 @@ only for the push sub command:
320321
.. code-block:: python
321322
322323
scv_grm_exclude = ('README.md', '.gitignore')
324+
325+
.. option:: -P <remote>, --push-remote <remote>, scv_push_remote
326+
327+
Push built docs to this remote. Default is **origin**.
328+
329+
This setting may also be specified in your conf.py file. It must be a string:
330+
331+
.. code-block:: python
332+
333+
scv_push_remote = 'origin2'

sphinxcontrib/versioning/__main__.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ def get_params(self, ctx):
121121
def cli(config, **options):
122122
"""Build versioned Sphinx docs for every branch and tag pushed to origin.
123123
124-
Supports only building locally with the "build" sub command or build and push to origin with the "push" sub command.
125-
For more information for either run them with their own --help.
124+
Supports only building locally with the "build" sub command or build and push to a remote with the "push" sub
125+
command. For more information for either run them with their own --help.
126126
127127
The options below are global and must be specified before the sub command name (e.g. -N build ...).
128128
\f
@@ -319,6 +319,7 @@ def build(config, rel_source, destination, **options):
319319
@click.option('-e', '--grm-exclude', multiple=True,
320320
help='If specified "git rm" will delete all files in REL_DEST except for these. Specify multiple times '
321321
'for more. Paths are relative to REL_DEST in DEST_BRANCH.')
322+
@click.option('-P', '--push-remote', help='Push built docs to this remote. Default is origin.')
322323
@click.argument('REL_SOURCE', nargs=-1, required=True)
323324
@click.argument('DEST_BRANCH')
324325
@click.argument('REL_DEST')
@@ -333,8 +334,8 @@ def push(ctx, config, rel_source, dest_branch, rel_dest, **options):
333334
REL_SOURCE is the path to the docs directory relative to the git root. If the source directory has moved around
334335
between git tags you can specify additional directories.
335336
336-
DEST_BRANCH is the branch name where generated docs will be committed to. The branch will then be pushed to origin.
337-
If there is a race condition with another job pushing to origin the docs will be re-generated and pushed again.
337+
DEST_BRANCH is the branch name where generated docs will be committed to. The branch will then be pushed to remote.
338+
If there is a race condition with another job pushing to remote the docs will be re-generated and pushed again.
338339
339340
REL_DEST is the path to the directory that will hold all generated docs for all versions relative to the git roof of
340341
DEST_BRANCH.
@@ -376,7 +377,7 @@ def push(ctx, config, rel_source, dest_branch, rel_dest, **options):
376377

377378
log.info('Attempting to push to branch %s on remote repository.', dest_branch)
378379
try:
379-
if commit_and_push(temp_dir, versions):
380+
if commit_and_push(temp_dir, config.push_remote, versions):
380381
return
381382
except GitError as exc:
382383
log.error(exc.message)

sphinxcontrib/versioning/git.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from sphinxcontrib.versioning.lib import TempDir
1313

14-
RE_FETCH_PUSH = re.compile(r'origin\t([A-Za-z0-9@:/._-]+) \((fetch|push)\)\n')
14+
RE_ALL_REMOTES = re.compile(r'([\w./-]+)\t([A-Za-z0-9@:/._-]+) \((fetch|push)\)\n')
1515
RE_REMOTE = re.compile(r'^(?P<sha>[0-9a-f]{5,40})\trefs/(?P<kind>heads|tags)/(?P<name>[\w./-]+(?:\^\{})?)$',
1616
re.MULTILINE)
1717
RE_UNIX_TIME = re.compile(r'^\d{10}$', re.MULTILINE)
@@ -313,14 +313,18 @@ def clone(local_root, new_root, branch, rel_dest, exclude):
313313
"""
314314
log = logging.getLogger(__name__)
315315
output = run_command(local_root, ['git', 'remote', '-v'])
316-
remote_urls = dict((m[1], m[0]) for m in RE_FETCH_PUSH.findall(output))
317-
if not remote_urls:
316+
matches = RE_ALL_REMOTES.findall(output)
317+
if not matches:
318+
raise GitError('Git repo has no remotes.', output)
319+
remotes = {m[0]: [m[1], ''] for m in matches if m[2] == 'fetch'}
320+
for match in (m for m in matches if m[2] == 'push'):
321+
remotes[match[0]][1] = match[1]
322+
if 'origin' not in remotes:
318323
raise GitError('Git repo missing remote "origin".', output)
319-
remote_push_url, remote_fetch_url = remote_urls['push'], remote_urls['fetch']
320324

321325
# Clone.
322326
try:
323-
run_command(new_root, ['git', 'clone', remote_fetch_url, '--depth=1', '--branch', branch, '.'])
327+
run_command(new_root, ['git', 'clone', remotes['origin'][0], '--depth=1', '--branch', branch, '.'])
324328
except CalledProcessError as exc:
325329
raise GitError('Failed to clone from remote repo URL.', exc.output)
326330

@@ -330,9 +334,11 @@ def clone(local_root, new_root, branch, rel_dest, exclude):
330334
except CalledProcessError as exc:
331335
raise GitError('Specified branch is not a real branch.', exc.output)
332336

333-
# Set push URL if different.
334-
if remote_fetch_url != remote_push_url:
335-
run_command(new_root, ['git', 'remote', 'set-url', '--push', 'origin', remote_push_url])
337+
# Copy all remotes from original repo.
338+
for name, (fetch, push) in remotes.items():
339+
if name != 'origin':
340+
run_command(new_root, ['git', 'remote', 'add', name, fetch])
341+
run_command(new_root, ['git', 'remote', 'set-url', '--push', name, push])
336342

337343
# Done if no exclude.
338344
if not exclude:
@@ -355,13 +361,14 @@ def clone(local_root, new_root, branch, rel_dest, exclude):
355361
run_command(new_root, ['git', 'checkout', '--'] + exclude_joined)
356362

357363

358-
def commit_and_push(local_root, versions):
359-
"""Commit changed, new, and deleted files in the repo and attempt to push the branch to origin.
364+
def commit_and_push(local_root, remote, versions):
365+
"""Commit changed, new, and deleted files in the repo and attempt to push the branch to the remote repository.
360366
361367
:raise CalledProcessError: Unhandled git command failure.
362368
:raise GitError: Conflicting changes made in remote by other client and bad git config for commits.
363369
364370
:param str local_root: Local path to git root directory.
371+
:param str remote: The git remote to push to.
365372
:param sphinxcontrib.versioning.versions.Versions versions: Versions class instance.
366373
367374
:return: If push succeeded.
@@ -410,7 +417,7 @@ def commit_and_push(local_root, versions):
410417

411418
# Push.
412419
try:
413-
run_command(local_root, ['git', 'push', 'origin', current_branch])
420+
run_command(local_root, ['git', 'push', remote, current_branch])
414421
except CalledProcessError as exc:
415422
if '[rejected]' in exc.output and '(fetch first)' in exc.output:
416423
log.debug('Remote has changed since cloning the repo. Must retry.')

sphinxcontrib/versioning/lib.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def __init__(self):
3434
self.git_root = None
3535
self.local_conf = None
3636
self.priority = None
37+
self.push_remote = 'origin'
3738
self.root_ref = 'master'
3839

3940
# Tuples.

tests/test__main__/test_arguments.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,10 @@ def test_sub_command_options(local_empty, push, source_cli, source_conf):
215215

216216
# Setup source(s).
217217
if source_cli:
218-
args += ['-aAbitT', '-B', 'x', '-p', 'branches', '-r', 'feature', '-s', 'semver', '-w', 'master', '-W', '[0-9]']
218+
args += ['-itT', '-p', 'branches', '-r', 'feature', '-s', 'semver', '-w', 'master', '-W', '[0-9]']
219+
args += ['-aAb', '-B', 'x']
219220
if push:
220-
args += ['-e' 'README.md']
221+
args += ['-e' 'README.md', '-P', 'rem']
221222
if source_conf:
222223
local_empty.ensure('docs', 'contents.rst')
223224
local_empty.ensure('docs', 'conf.py').write(
@@ -228,6 +229,7 @@ def test_sub_command_options(local_empty, push, source_cli, source_conf):
228229
'scv_greatest_tag = True\n'
229230
'scv_invert = True\n'
230231
'scv_priority = "tags"\n'
232+
'scv_push_remote = "origin2"\n'
231233
'scv_recent_tag = True\n'
232234
'scv_root_ref = "other"\n'
233235
'scv_show_banner = True\n'
@@ -257,6 +259,7 @@ def test_sub_command_options(local_empty, push, source_cli, source_conf):
257259
assert config.whitelist_tags == ('[0-9]',)
258260
if push:
259261
assert config.grm_exclude == ('README.md',)
262+
assert config.push_remote == 'rem'
260263
elif source_conf:
261264
assert config.banner_greatest_tag is True
262265
assert config.banner_main_ref == 'y'
@@ -272,6 +275,7 @@ def test_sub_command_options(local_empty, push, source_cli, source_conf):
272275
assert config.whitelist_tags.pattern == '^[0-9]$'
273276
if push:
274277
assert config.grm_exclude == ('README.rst',)
278+
assert config.push_remote == 'origin2'
275279
else:
276280
assert config.banner_greatest_tag is False
277281
assert config.banner_main_ref == 'master'
@@ -287,6 +291,7 @@ def test_sub_command_options(local_empty, push, source_cli, source_conf):
287291
assert config.whitelist_tags == tuple()
288292
if push:
289293
assert config.grm_exclude == tuple()
294+
assert config.push_remote == 'origin'
290295

291296

292297
@pytest.mark.parametrize('push', [False, True])

tests/test__main__/test_main_push_scenarios.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,43 @@ def test_race(tmpdir, local_docs_ghp, remote, run, urls, give_up):
164164
assert actual == 'Orphaned branch for HTML docs.changed'
165165

166166

167+
def test_second_remote(tmpdir, local_docs_ghp, run, urls):
168+
"""Test pushing to a non-origin remote.
169+
170+
:param tmpdir: pytest fixture.
171+
:param local_docs_ghp: conftest fixture.
172+
:param run: conftest fixture.
173+
:param urls: conftest fixture.
174+
"""
175+
# Error out because origin2 doesn't exist yet.
176+
with pytest.raises(CalledProcessError) as exc:
177+
run(local_docs_ghp, ['sphinx-versioning', 'push', '.', 'gh-pages', '.', '-P', 'origin2'])
178+
assert 'Traceback' not in exc.value.output
179+
assert 'Failed to push to remote.' in exc.value.output
180+
assert "fatal: 'origin2' does not appear to be a git repository" in exc.value.output
181+
182+
# Create origin2.
183+
origin2 = tmpdir.ensure_dir('origin2')
184+
run(origin2, ['git', 'init', '--bare'])
185+
run(local_docs_ghp, ['git', 'remote', 'add', 'origin2', origin2])
186+
run(local_docs_ghp, ['git', 'push', 'origin2', 'gh-pages'])
187+
188+
# Run again.
189+
output = run(local_docs_ghp, ['sphinx-versioning', 'push', '.', 'gh-pages', '.', '-P', 'origin2'])
190+
assert 'Traceback' not in output
191+
assert 'Successfully pushed to remote repository.' in output
192+
193+
# Check files.
194+
run(local_docs_ghp, ['git', 'checkout', 'origin2/gh-pages'])
195+
run(local_docs_ghp, ['git', 'pull', 'origin2', 'gh-pages'])
196+
urls(local_docs_ghp.join('contents.html'), ['<li><a href="master/contents.html">master</a></li>'])
197+
urls(local_docs_ghp.join('master', 'contents.html'), ['<li><a href="contents.html">master</a></li>'])
198+
run(local_docs_ghp, ['git', 'checkout', 'origin/gh-pages'])
199+
run(local_docs_ghp, ['git', 'pull', 'origin', 'gh-pages'])
200+
assert not local_docs_ghp.join('contents.html').check()
201+
assert not local_docs_ghp.join('master').check()
202+
203+
167204
def test_error_clone_failure(local_docs, run):
168205
"""Test DEST_BRANCH doesn't exist.
169206
@@ -221,8 +258,13 @@ def test_bad_git_config(local_docs_ghp, run):
221258
# Invalidate lock file.
222259
tmp_repo = py.path.local(re.findall(r'"cwd": "([^"]+)"', line.decode('utf-8'))[0])
223260
assert tmp_repo.check(dir=True)
224-
run(tmp_repo, ['git', 'config', 'user.useConfigOnly', 'true'])
225-
run(tmp_repo, ['git', 'config', 'user.email', '(none)'])
261+
for _ in range(3):
262+
try:
263+
run(tmp_repo, ['git', 'config', 'user.useConfigOnly', 'true'])
264+
run(tmp_repo, ['git', 'config', 'user.email', '(none)'])
265+
except CalledProcessError:
266+
continue
267+
break
226268
caused = True
227269
output_lines.append(proc.communicate()[0])
228270
output = b''.join(output_lines).decode('utf-8')

tests/test_git/test_clone.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,19 @@ def test_bad_branch_rel_dest_exclude(tmpdir, local, run):
147147
clone(str(local), str(tmpdir.ensure_dir('new_root3')), 'master', 'unknown', ['README'])
148148
assert "pathspec 'unknown' did not match any files" in exc.value.output
149149

150-
# No remote.
151-
run(local, ['git', 'remote', 'rm', 'origin'])
150+
# No origin.
151+
run(local, ['git', 'remote', 'rename', 'origin', 'origin2'])
152152
with pytest.raises(GitError) as exc:
153153
clone(str(local), str(tmpdir.ensure_dir('new_root3')), 'master', '.', None)
154154
assert 'Git repo missing remote "origin".' in exc.value.message
155+
assert 'origin2\t' in exc.value.output
156+
assert 'origin\t' not in exc.value.output
157+
158+
# No remote.
159+
run(local, ['git', 'remote', 'rm', 'origin2'])
160+
with pytest.raises(GitError) as exc:
161+
clone(str(local), str(tmpdir.ensure_dir('new_root3')), 'master', '.', None)
162+
assert 'Git repo has no remotes.' in exc.value.message
155163
assert not exc.value.output
156164

157165
# Bad remote.
@@ -161,21 +169,33 @@ def test_bad_branch_rel_dest_exclude(tmpdir, local, run):
161169
assert "repository '{}' does not exist".format(local.join('does_not_exist')) in exc.value.output
162170

163171

164-
def test_fetch_push_remotes(tmpdir, local, remote, run):
165-
"""Test different fetch/push URLs being carried over.
172+
def test_multiple_remotes(tmpdir, local, remote, run):
173+
"""Test multiple remote URLs being carried over.
166174
167175
:param tmpdir: pytest fixture.
168176
:param local: conftest fixture.
169177
:param remote: conftest fixture.
170178
:param run: conftest fixture.
171179
"""
172-
remote_push = tmpdir.ensure_dir('remote_push')
173-
run(remote_push, ['git', 'init', '--bare'])
174-
run(local, ['git', 'remote', 'set-url', '--push', 'origin', str(remote_push)])
180+
origin_push = tmpdir.ensure_dir('origin_push')
181+
run(origin_push, ['git', 'init', '--bare'])
182+
run(local, ['git', 'remote', 'set-url', '--push', 'origin', str(origin_push)])
183+
origin2_fetch = tmpdir.ensure_dir('origin2_fetch')
184+
run(origin2_fetch, ['git', 'init', '--bare'])
185+
run(local, ['git', 'remote', 'add', 'origin2', str(origin2_fetch)])
186+
origin2_push = tmpdir.ensure_dir('origin2_push')
187+
run(origin2_push, ['git', 'init', '--bare'])
188+
run(local, ['git', 'remote', 'set-url', '--push', 'origin2', str(origin2_push)])
175189

176190
new_root = tmpdir.ensure_dir('new_root')
177191
clone(str(local), str(new_root), 'master', '', None)
178192

179193
output = run(new_root, ['git', 'remote', '-v'])
180-
expected = 'origin\t{} (fetch)\norigin\t{} (push)\n'.format(remote, remote_push)
181-
assert output == expected
194+
actual = output.strip().splitlines()
195+
expected = [
196+
'origin\t{} (fetch)'.format(remote),
197+
'origin\t{} (push)'.format(origin_push),
198+
'origin2\t{} (fetch)'.format(origin2_fetch),
199+
'origin2\t{} (push)'.format(origin2_push),
200+
]
201+
assert actual == expected

0 commit comments

Comments
 (0)