Skip to content

Commit efcb008

Browse files
authored
Support --target_branch (#877)
Adds support for specifying the branch to which to build docs in the target repo. This will make release times smoother because we can continue to build the docs but send them to the `staging` branch. It *might* allow more fancy pull request testing by allowing us to send pull requests to their own branch. If the branch already exists we just check it out and build the docs onto it and then push it. If it doesn't exist then things are a little more complicated. First we check if the `master` branch exists. It won't in an entirely fresh repo which we often use for testing. If it doesn't exist we create an empty commit in the master branch to "anchor" it. Once we're sure that the master branch exists we fork the new target branch from the master branch. Pushing the built docs gets a bit more complex in two ways: 1. If the target branch is new we always push it, even if there haven't been any changes. This keeps things consistent, both for easy testing and for the potential PR testing work that I mentioned above. 2. If we had to create an empty master branch we push that too. This makes sure that non-empty repos have a master branch which is a fairly useful invariant.
1 parent ec4ea4e commit efcb008

File tree

6 files changed

+197
-55
lines changed

6 files changed

+197
-55
lines changed

build_docs.pl

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ BEGIN
2929
run $Opts
3030
build_chunked build_single build_pdf
3131
proc_man
32-
sha_for
3332
timestamp
3433
write_html_redirect
3534
write_nginx_redirects
@@ -462,6 +461,7 @@ sub init_repos {
462461
url => $Opts->{target_repo},
463462
reference => $reference_dir,
464463
destination => dir( $target_repo_checkout ),
464+
branch => $Opts->{target_branch} || 'master',
465465
);
466466
delete $child_dirs{ $target_repo->git_dir->absolute };
467467
$tracker_path = "$target_repo_checkout/$tracker_path";
@@ -555,30 +555,23 @@ sub push_changes {
555555
$build_dir->file('revision.txt')
556556
->spew( iomode => '>:utf8', ES::Repo->all_repo_branches );
557557

558-
say 'Preparing commit';
559-
run qw(git add -A);
560-
561-
if ( run qw(git status -s --) ) {
558+
# Check if there are changes before committing and save that because if
559+
# there are changes we'll commit them. Once we've committed them checking
560+
# if there are changes will say "no" because there aren't changes
561+
# *any more*. Thus we build $has_changes and $should_push here and not
562+
# in the `if` statements below.
563+
my $has_changes = $target_repo->outstanding_changes;
564+
my $should_push = $has_changes || $target_repo->new_branch;
565+
if ( $has_changes ) {
566+
say 'Preparing commit';
562567
build_sitemap($build_dir);
563-
run qw(git add -A);
564568
say "Commiting changes";
565-
run qw(git commit -m), 'Updated docs';
569+
$target_repo->commit;
566570
}
567-
568-
my $remote_sha = eval {
569-
my $remote = run qw(git rev-parse --symbolic-full-name @{u});
570-
chomp $remote;
571-
return sha_for($remote);
572-
} || '';
573-
574-
if ( sha_for('HEAD') ne $remote_sha ) {
575-
say "Pushing changes to bare repo";
576-
run qw(git push origin HEAD );
577-
local $ENV{GIT_DIR} = $target_repo->git_dir if $target_repo;
571+
if ( $should_push ) {
578572
say "Pushing changes";
579-
run qw(git push origin HEAD );
580-
}
581-
else {
573+
$target_repo->push_changes;
574+
} else {
582575
say "No changes to push";
583576
}
584577
}
@@ -760,6 +753,7 @@ sub command_line_opts {
760753
'toc',
761754
# Options only compatible with --all
762755
'all',
756+
'target_branch=s',
763757
'target_repo=s',
764758
'keep_hash',
765759
'linkcheckonly',
@@ -820,6 +814,7 @@ sub usage {
820814
--sub_dir Use a directory as a branch of some repo
821815
(eg --sub_dir elasticsearch:master:~/Code/elasticsearch)
822816
--target_repo Repository to which to commit docs
817+
--target_branch Branch to which to commit docs
823818
--user Specify which GitHub user to use, if not your own
824819
825820
General Opts:
@@ -868,6 +863,7 @@ sub check_opts {
868863
die('--reference not compatible with --doc') if $Opts->{reference};
869864
die('--skiplinkcheck not compatible with --doc') if $Opts->{skiplinkcheck};
870865
die('--sub_dir not compatible with --doc') if $Opts->{sub_dir};
866+
die('--target_branch not compatible with --doc') if $Opts->{target_branch};
871867
die('--target_repo not compatible with --doc') if $Opts->{target_repo};
872868
die('--user not compatible with --doc') if $Opts->{user};
873869
} else {

integtest/spec/all_books_change_detection_spec.rb

Lines changed: 77 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,50 @@
44
# Assertions about when books are rebuilt based on changes in source
55
# repositories or the book's configuration.
66
RSpec.describe 'building all books' do
7+
class Config
8+
attr_accessor :target_branch
9+
10+
def initialize
11+
@target_branch = nil
12+
end
13+
end
714
describe 'change detection' do
815
def self.build_twice(
916
before_first_build:,
10-
before_second_build: ->(src) {}
17+
before_second_build:
1118
)
1219
convert_before do |src, dest|
20+
config = Config.new
1321
# Allow the caller to customize the source.
14-
before_first_build.call(src)
22+
before_first_build.call(src, config)
1523

1624
# Convert the first time. This should build the docs.
17-
dest.convert_all src.conf
25+
dest.convert_all src.conf, target_branch: config.target_branch
1826

1927
# Take some action between the builds.
20-
before_second_build.call(src)
28+
before_second_build.call(src, config)
2129

2230
# Convert the second time.
23-
dest.convert_all src.conf
31+
dest.convert_all src.conf, target_branch: config.target_branch
2432

2533
# Checkout the files so we can assert about them.
26-
dest.checkout_conversion
34+
dest.checkout_conversion branch: config.target_branch
2735
end
2836
include_context 'build one book twice'
2937
end
3038

3139
def self.build_one_book_out_of_one_repo_twice(
32-
before_first_build: ->(src) {},
33-
before_second_build: ->(src) {}
40+
before_first_build: ->(src, config) {},
41+
before_second_build: ->(src, config) {}
3442
)
3543
build_twice(
36-
before_first_build: lambda do |src|
44+
before_first_build: lambda do |src, config|
3745
repo = src.repo_with_index 'repo', 'Some text.'
3846
book = src.book 'Test'
3947
book.source repo, 'index.asciidoc'
4048

4149
# Allow the caller to customize the source
42-
before_first_build.call(src)
50+
before_first_build.call src, config
4351
end,
4452
before_second_build: before_second_build
4553
)
@@ -50,13 +58,15 @@ def self.build_one_book_out_of_two_repos_twice(
5058
before_second_build: ->(src) {}
5159
)
5260
build_twice(
53-
before_first_build: lambda do |src|
61+
before_first_build: lambda do |src, _config|
5462
src.simple_include
5563

5664
# Allow the caller to customize the source
57-
before_first_build.call(src)
65+
before_first_build.call src
5866
end,
59-
before_second_build: before_second_build
67+
before_second_build: lambda do |src, _config|
68+
before_second_build.call src
69+
end
6070
)
6171
end
6272

@@ -100,25 +110,51 @@ def self.build_one_book_out_of_two_repos_twice(
100110
build_one_book_out_of_one_repo_twice
101111
include_examples 'second build is noop'
102112
end
103-
104-
context 'because there are unrelated changes source repo' do
113+
context 'even when there are unrelated changes source repo' do
105114
build_one_book_out_of_one_repo_twice(
106-
before_second_build: lambda do |src|
115+
before_second_build: lambda do |src, _config|
107116
repo = src.repo 'repo'
108117
repo.write 'garbage', 'junk'
109118
repo.commit 'adding junk'
110119
end
111120
)
112121
include_examples 'second build is noop'
113122
end
123+
context 'even when there is a target_branch' do
124+
build_one_book_out_of_one_repo_twice(
125+
before_first_build: lambda do |_src, config|
126+
config.target_branch = 'new_target'
127+
end
128+
)
129+
include_examples 'second build is noop'
130+
end
131+
context 'even when there is a new target branch' do
132+
# Adding a new target branch will cause us to fork it from the
133+
# master branch which so we won't have to rebuild the book *but*
134+
# we push anyway so the new target branch is available.
135+
build_one_book_out_of_one_repo_twice(
136+
before_second_build: lambda do |_src, config|
137+
config.target_branch = 'new_target'
138+
end
139+
)
140+
context 'the second build' do
141+
let(:out) { outputs[1] }
142+
it "doesn't print that it is building any books" do
143+
expect(out).not_to include(': Building ')
144+
end
145+
it "doesn't print that it is commiting changes" do
146+
expect(out).not_to include('Commiting changes')
147+
end
148+
it 'prints that it is pushing changes' do
149+
expect(out).to include('Pushing changes')
150+
end
151+
end
152+
end
114153
end
115154
context "when the second build isn't a noop" do
116-
let(:latest_revision) { 'changed text' }
117-
let(:new_text) { 'New text.' }
118-
119155
context 'because the source repo changes' do
120156
build_one_book_out_of_one_repo_twice(
121-
before_second_build: lambda do |src|
157+
before_second_build: lambda do |src, _config|
122158
repo = src.repo 'repo'
123159
repo.write 'index.asciidoc', <<~ASCIIDOC
124160
= Title
@@ -130,35 +166,49 @@ def self.build_one_book_out_of_two_repos_twice(
130166
repo.commit 'changed text'
131167
end
132168
)
169+
let(:latest_revision) { 'changed text' }
170+
let(:new_text) { 'New text.' }
133171
include_examples 'second build is not a noop'
134172
end
135173
context 'because the book changes from asciidoc to asciidoctor' do
136174
build_one_book_out_of_one_repo_twice(
137-
before_first_build: lambda do |src|
175+
before_first_build: lambda do |src, _config|
138176
book = src.book 'Test'
139177
book.asciidoctor = false
140178
end,
141-
before_second_build: lambda do |src|
179+
before_second_build: lambda do |src, _config|
142180
book = src.book 'Test'
143181
book.asciidoctor = true
144182
end
145183
)
146-
# We didn't change the revision....
147184
let(:latest_revision) { 'init' }
148-
# And the text hasn't changed
149185
let(:new_text) { 'Some text.' }
150186
include_examples 'second build is not a noop'
151187
end
152188
context 'because the book changes from asciidoctor to asciidoc' do
153189
build_one_book_out_of_one_repo_twice(
154-
before_second_build: lambda do |src|
190+
before_second_build: lambda do |src, _config|
155191
book = src.book 'Test'
156192
book.asciidoctor = false
157193
end
158194
)
159-
# We didn't change the revision....
160195
let(:latest_revision) { 'init' }
161-
# And the text hasn't changed
196+
let(:new_text) { 'Some text.' }
197+
include_examples 'second build is not a noop'
198+
end
199+
context 'because we remove the target_branch' do
200+
# Removing the target branch causes us to build into the *empty*
201+
# master branch. Being empty, there aren't any books in it to
202+
# consider "already built".
203+
build_one_book_out_of_one_repo_twice(
204+
before_first_build: lambda do |_src, config|
205+
config.target_branch = 'new_target'
206+
end,
207+
before_second_build: lambda do |_src, config|
208+
config.target_branch = nil # nil means don't override
209+
end
210+
)
211+
let(:latest_revision) { 'init' }
162212
let(:new_text) { 'Some text.' }
163213
include_examples 'second build is not a noop'
164214
end

integtest/spec/all_books_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,16 @@ def self.single_book_built_by_two_repos
112112
let(:latest_revision) { 'init' }
113113
include_examples 'book basics', 'Test', 'test'
114114
end
115+
context 'when target_branch is specified' do
116+
convert_all_before_context target_branch: 'new_branch' do |src|
117+
repo = src.repo_with_index 'repo', 'Some text.'
118+
book = src.book 'Test'
119+
book.source repo, 'index.asciidoc'
120+
end
121+
let(:latest_revision) { 'init' }
122+
include_examples 'book basics', 'Test', 'test'
123+
it 'prints that it is forking the new branch from master' do
124+
expect(out).to include('target_repo: Forking <new_branch> from master')
125+
end
126+
end
115127
end

integtest/spec/helper/dest.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,23 @@ def convert_single(from, to,
4949

5050
##
5151
# Convert a conf file worth of books and check it out.
52-
def convert_all(conf, expect_failure: false)
52+
def convert_all(conf, expect_failure: false, target_branch: nil)
5353
cmd = %W[
5454
--all
5555
--push
5656
--target_repo #{bare_repo}
5757
--conf #{conf}
5858
]
59+
cmd += ['--target_branch', target_branch] if target_branch
5960
run_convert(cmd, expect_failure)
6061
end
6162

6263
##
6364
# Checks out the results of the last call to convert_all
64-
def checkout_conversion
65-
sh "git clone #{bare_repo} #{@dest}"
65+
def checkout_conversion(branch: nil)
66+
branch_cmd = ''
67+
branch_cmd = "--branch #{branch} " if branch
68+
sh "git clone #{branch_cmd}#{bare_repo} #{@dest}"
6669
end
6770

6871
private

integtest/spec/helper/dsl/convert_all.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ module ConvertAll
88
# uses it to:
99
# 1. Create source repositories and write them
1010
# 2. Configure the books that should be built
11-
def convert_all_before_context(relative_conf: false)
11+
def convert_all_before_context(relative_conf: false, target_branch: nil)
1212
convert_before do |src, dest|
1313
yield src
14-
dest.convert_all src.conf(relative_path: relative_conf)
15-
dest.checkout_conversion
14+
dest.convert_all src.conf(relative_path: relative_conf),
15+
target_branch: target_branch
16+
dest.checkout_conversion branch: target_branch
1617
end
1718
include_examples 'convert all'
1819
end

0 commit comments

Comments
 (0)