modulesync 4.4.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b756f006e9c39eef2b7a94ec1f896866f22f88099978530b2e587565dc9826c8
4
- data.tar.gz: 1d5fb83e96ee7974a09b12f91e6fea79997554fbe959d8ad6c29938d41f76ae8
3
+ metadata.gz: 42cf2d8364d939be480302cbb0856e69df96468c5b35a460c29380dbb1455049
4
+ data.tar.gz: 93ee25b205bd960afe750188b5e1bf7c640aace8bdb91aac8345aed93d980a23
5
5
  SHA512:
6
- metadata.gz: b838e8aeaf52e6d0f2ff3dfbdc1a6c0ede1d1188862de5e6d7205632d94383782ef3aeaa2fb1150b18469a46c582ded2fad29621048657c4922453d69e4821ed
7
- data.tar.gz: 19160e011a602778c435dfa18e5e7583fada6a7bfdca7ee125be0553b0b8acc795f10f986821bd3783a7dba9f5e8e1d6815565803f3411ab61b0a61c552e1d1b
6
+ metadata.gz: de6493978a435981fdc1b28bb0d937df4631f8eea6cdfe820bfb543b829a1c51d77672f31e510caaf18ef62754ead0988beab47040ad1ecc8c6e75723517d3fa
7
+ data.tar.gz: 46094244ce217f1e3dfbfd57d87b6aba64d0533dfe42b94d96ef48a66d2ef17dcc0cae9e24e08d54344bb0b9fa1553f1eb8898170489b0b79bfc9fab9acb5081
@@ -16,16 +16,16 @@ jobs:
16
16
  outputs:
17
17
  ruby: ${{ steps.ruby.outputs.versions }}
18
18
  steps:
19
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
19
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20
20
  - name: Setup ruby
21
- uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
21
+ uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
22
22
  with:
23
23
  ruby-version: "3.4"
24
24
  bundler-cache: true
25
25
  - name: Run linter
26
26
  run: bundle exec rake rubocop
27
27
  - id: ruby
28
- uses: voxpupuli/ruby-version@656370e339050da63b86b1c631f5f88a3f4c0803 # 1.0.1
28
+ uses: voxpupuli/ruby-version@be0467ce7366d206b2e87f5b7a032a030120b7cb # 2.0.0
29
29
 
30
30
  test:
31
31
  name: "Ruby ${{ matrix.ruby }}"
@@ -36,9 +36,9 @@ jobs:
36
36
  matrix:
37
37
  ruby: ${{ fromJSON(needs.rubocop_and_matrix.outputs.ruby) }}
38
38
  steps:
39
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
39
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
40
40
  - name: Install Ruby ${{ matrix.ruby }}
41
- uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
41
+ uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
42
42
  with:
43
43
  ruby-version: ${{ matrix.ruby }}
44
44
  bundler-cache: true
@@ -14,4 +14,4 @@ jobs:
14
14
  permissions:
15
15
  pull-requests: write
16
16
  steps:
17
- - uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
17
+ - uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0
@@ -15,9 +15,9 @@ jobs:
15
15
  name: Build the gem
16
16
  runs-on: ubuntu-24.04
17
17
  steps:
18
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
18
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
19
  - name: Install Ruby
20
- uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
20
+ uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
21
21
  with:
22
22
  ruby-version: 'ruby'
23
23
  - name: Build gem
@@ -96,7 +96,7 @@ jobs:
96
96
  with:
97
97
  name: gem-artifact
98
98
  - name: Install Ruby
99
- uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
99
+ uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
100
100
  with:
101
101
  ruby-version: 'ruby'
102
102
  - name: Wait for release to propagate
data/CHANGELOG.md CHANGED
@@ -2,9 +2,30 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [4.4.0](https://github.com/voxpupuli/modulesync/tree/4.4.0) (2026-07-09)
5
+ ## [4.6.0](https://github.com/voxpupuli/modulesync/tree/4.6.0) (2026-07-31)
6
6
 
7
- [Full Changelog](https://github.com/voxpupuli/modulesync/compare/v4.3.0...4.4.0)
7
+ [Full Changelog](https://github.com/voxpupuli/modulesync/compare/v4.5.0...4.6.0)
8
+
9
+ **Implemented enhancements:**
10
+
11
+ - feat: add support for cryptographic signing and signoff options in commits and tags [\#368](https://github.com/voxpupuli/modulesync/pull/368) ([rwaffen](https://github.com/rwaffen))
12
+
13
+ ## [v4.5.0](https://github.com/voxpupuli/modulesync/tree/v4.5.0) (2026-07-15)
14
+
15
+ [Full Changelog](https://github.com/voxpupuli/modulesync/compare/v4.4.0...v4.5.0)
16
+
17
+ **Implemented enhancements:**
18
+
19
+ - feat: update pr/mr title if needed [\#358](https://github.com/voxpupuli/modulesync/pull/358) ([rwaffen](https://github.com/rwaffen))
20
+ - feat: add option to rebase modulesync branch with the latest changes from the origin’s default branch [\#356](https://github.com/voxpupuli/modulesync/pull/356) ([rwaffen](https://github.com/rwaffen))
21
+
22
+ **Merged pull requests:**
23
+
24
+ - Enhance README.md [\#298](https://github.com/voxpupuli/modulesync/pull/298) ([bastelfreak](https://github.com/bastelfreak))
25
+
26
+ ## [v4.4.0](https://github.com/voxpupuli/modulesync/tree/v4.4.0) (2026-07-09)
27
+
28
+ [Full Changelog](https://github.com/voxpupuli/modulesync/compare/v4.3.0...v4.4.0)
8
29
 
9
30
  **Implemented enhancements:**
10
31
 
data/README.md CHANGED
@@ -1,5 +1,4 @@
1
- ModuleSync
2
- ===========
1
+ # ModuleSync
3
2
 
4
3
  [![License](https://img.shields.io/github/license/voxpupuli/modulesync.svg)](https://github.com/voxpupuli/modulesync/blob/master/LICENSE)
5
4
  [![Test](https://github.com/voxpupuli/modulesync/actions/workflows/ci.yml/badge.svg)](https://github.com/voxpupuli/modulesync/actions/workflows/ci.yml)
@@ -9,198 +8,202 @@ ModuleSync
9
8
  [![RubyGem Downloads](https://img.shields.io/gem/dt/modulesync.svg)](https://rubygems.org/gems/modulesync)
10
9
  [![Donated by Puppet Inc](https://img.shields.io/badge/donated%20by-Puppet%20Inc-fb7047.svg)](#transfer-notice)
11
10
 
12
- Table of Contents
13
- -----------------
14
-
15
- 1. [Usage TLDR](#usage-tldr)
16
- 2. [Overview](#overview)
17
- 3. [How it works](#how-it-works)
18
- 4. [Installing](#installing)
19
- 5. [Workflow](#workflow)
20
- 6. [The Templates](#the-templates)
21
-
22
- Usage TLDR
23
- ----------
24
-
25
- ```
11
+ ## Table of Contents
12
+
13
+ - [ModuleSync](#modulesync)
14
+ - [Table of Contents](#table-of-contents)
15
+ - [Usage TLDR](#usage-tldr)
16
+ - [Overview](#overview)
17
+ - [How It Works](#how-it-works)
18
+ - [Installing](#installing)
19
+ - [Workflow](#workflow)
20
+ - [Default mode](#default-mode)
21
+ - [Make changes](#make-changes)
22
+ - [Dry-run](#dry-run)
23
+ - [Offline support](#offline-support)
24
+ - [Damage mode](#damage-mode)
25
+ - [Automating Updates](#automating-updates)
26
+ - [Submitting PRs/MRs to GitHub or GitLab](#submitting-prsmrs-to-github-or-gitlab)
27
+ - [Using Forks and Non-master branches](#using-forks-and-non-master-branches)
28
+ - [Dry-run](#dry-run-1)
29
+ - [Damage mode](#damage-mode-1)
30
+ - [Configuring ModuleSync defaults](#configuring-modulesync-defaults)
31
+ - [Example](#example)
32
+ - [GitHub](#github)
33
+ - [GitLab](#gitlab)
34
+ - [Gerrit](#gerrit)
35
+ - [Filtering Repositories](#filtering-repositories)
36
+ - [Pushing to a different remote branch](#pushing-to-a-different-remote-branch)
37
+ - [Automating updates](#automating-updates-1)
38
+ - [Updating metadata.json](#updating-metadatajson)
39
+ - [Tagging repositories](#tagging-repositories)
40
+ - [Setting the tag pattern](#setting-the-tag-pattern)
41
+ - [Executing bundle command with a shared bundle path and gemfile](#executing-bundle-command-with-a-shared-bundle-path-and-gemfile)
42
+ - [Updating the CHANGELOG](#updating-the-changelog)
43
+ - [Working with templates](#working-with-templates)
44
+ - [The Templates](#the-templates)
45
+ - [Transfer Notice](#transfer-notice)
46
+ - [License](#license)
47
+ - [Release information](#release-information)
48
+
49
+ ## Usage TLDR
50
+
51
+ ```shell
26
52
  gem install modulesync
27
53
  msync --help
28
54
  ```
29
55
 
30
- Overview
31
- --------
32
-
33
- ModuleSync was written as a simple script with ERB templates to help the
34
- Puppet Labs module engineers manage the zoo of Puppet modules on GitHub, and
35
- has now been restructured and generalized to be used within other
36
- organizations. Puppet modules within an organization tend to have a number of
37
- meta-files that are identical or very similar between modules, such as the
38
- `Gemfile`, `.travis.yml`, `.gitignore`, or `spec_helper.rb`. If a file needs to
39
- change in one module, it likely needs to change in the same way in every other
40
- module that the organization manages.
41
-
42
- One approach to this problem is to use sed in a bash for loop on the modules to
43
- make a single change across every module. This approach falls short if there is
44
- a single file that is purposefully different than the others, but still needs
45
- to be managed. Moreover, this approach does not help if two files are
46
- structured differently but need to be changed with the same meaning; for
47
- instance, if the .travis.yml of one module uses a list of environments to
48
- include, and another uses a matrix of includes with a list of excludes, adding
49
- a test environment to both modules will require entirely different approaches.
50
-
51
- ModuleSync provides the advantage of defining a standard template for each
52
- file to follow, so it becomes clear what a file is supposed to look like. Two
53
- files with the same semantics should also have the same syntax. A difference
54
- between two files should have clear reasons, and old cruft should not be left
55
- in files of one module as the files of another module march forward.
56
-
57
- Another advantage of ModuleSync is the ability to run in no-op mode, which
58
- makes local changes and shows the diffs, but does not make permanent changes in
59
- the remote repository.
60
-
61
- How It Works
62
- ------------
63
-
64
- ModuleSync is a gem that uses the GitHub workflow to clone, update, and push module
65
- repositories. It expects to be activated from a directory containing
66
- configuration for modulesync and the modules, or you can pass it the location
67
- of this configuration directory. [The configuration for the Vox Pupuli
68
- modules](https://github.com/voxpupuli/modulesync_config), can be used as an
69
- example for your own configuration. The configuration directory contains a
70
- directory called moduleroot which mirrors the structure of a module. The files
71
- in the moduleroot are ERB templates, and MUST be named after the target file,
72
- with `.erb.` appended. The templates are
73
- rendered using values from a file called `config_defaults.yml` in the root (not
74
- moduleroot) of the configuration directory. The default values can be
75
- overridden or extended by adding a file called `.sync.yml` to the module itself.
76
- This allows us to, for example, have a set of "required" gems that are added
77
- to all Gemfiles, and a set of "optional" gems that a single module might add.
78
-
79
- Within the templates, values can be accessed in the `@configs` hash, which is
80
- merged from the values under the keys `:global` and the target file name (no
81
- `.erb` suffix).
82
-
83
- The list of modules to manage is in `managed_modules.yml` in the configuration
84
- directory. This lists just the names of the modules to be managed.
85
-
86
- ModuleSync can be called from the command line with parameters to change the
87
- branch you're working on or the remote to clone from and push to. You can also
88
- define these parameters in a file named `modulesync.yml` in the configuration
89
- directory.
90
-
91
- Installing
92
- ----------
56
+ ## Overview
93
57
 
94
- ```
58
+ ModuleSync was written as a simple script with ERB templates to help the Puppet Labs module engineers manage the zoo of Puppet modules on GitHub, and has now been restructured and generalized to be used within other organizations.
59
+ Puppet modules within an organization tend to have a number of meta-files that are identical or very similar between modules, such as the `Gemfile`, `.travis.yml`, `.gitignore`, or `spec_helper.rb`.
60
+ If a file needs to change in one module, it likely needs to change in the same way in every other module that the organization manages.
61
+
62
+ One approach to this problem is to use sed in a bash for loop on the modules to make a single change across every module.
63
+ This approach falls short if there is a single file that is purposefully different than the others, but still needs to be managed.
64
+ Moreover, this approach does not help if two files are structured differently but need to be changed with the same meaning;
65
+ for instance, if the .travis.yml of one module uses a list of environments to include, and another uses a matrix of includes with a list of excludes, adding a test environment to both modules will require entirely different approaches.
66
+
67
+ ModuleSync provides the advantage of defining a standard template for each file to follow, so it becomes clear what a file is supposed to look like.
68
+ Two files with the same semantics should also have the same syntax.
69
+ A difference between two files should have clear reasons, and old cruft should not be left in files of one module as the files of another module march forward.
70
+
71
+ Another advantage of ModuleSync is the ability to run in no-op mode, which makes local changes and shows the diffs, but does not make permanent changes in the remote repository.
72
+
73
+ ## How It Works
74
+
75
+ ModuleSync is a gem that uses the GitHub workflow to clone, update, and push module repositories.
76
+ It expects to be activated from a directory containing configuration for modulesync and the modules, or you can pass it the location of this configuration directory.
77
+ [The configuration for the Vox Pupuli modules](https://github.com/voxpupuli/modulesync_configs), can be used as an example for your own configuration.
78
+ The configuration directory contains a directory called moduleroot which mirrors the structure of a module.
79
+ The files in the moduleroot are ERB templates, and MUST be named after the target file, with `.erb.` appended.
80
+ The templates are rendered using values from a file called `config_defaults.yml` in the root (not moduleroot) of the configuration directory.
81
+ The default values can be overridden or extended by adding a file called `.sync.yml` to the module itself.
82
+ This allows us to, for example, have a set of "required" gems that are added to all Gemfiles, and a set of "optional" gems that a single module might add.
83
+
84
+ Within the templates, values can be accessed in the `@configs` hash, which is merged from the values under the keys `:global` and the target file name (no `.erb` suffix).
85
+
86
+ The list of modules to manage is in `managed_modules.yml` in the configuration directory.
87
+ This lists just the names of the modules to be managed.
88
+
89
+ ModuleSync can be called from the command line with parameters to change the branch you're working on or the remote to clone from and push to.
90
+ You can also define these parameters in a file named `modulesync.yml` in the configuration directory.
91
+
92
+ ## Installing
93
+
94
+ ```shell
95
95
  gem install modulesync
96
96
  ```
97
97
 
98
98
  For developers:
99
99
 
100
- ```
100
+ ```shell
101
101
  gem build modulesync.gemspec
102
102
  gem install modulesync-*.gem
103
103
  ```
104
104
 
105
- Workflow
106
- --------
105
+ ## Workflow
107
106
 
108
107
  ### Default mode
109
108
 
110
- With no additional arguments, ModuleSync clones modules from the puppetlabs
111
- github organization and pushes to the master branch.
109
+ With no additional arguments, ModuleSync clones modules from the puppetlabs github organization and pushes to the master branch.
112
110
 
113
111
  #### Make changes
114
112
 
115
- Make changes to a file in the moduleroot. For sanity's sake you should commit
116
- and push these changes, but in this mode the update will be rendered from the
117
- state of the files locally.
113
+ Make changes to a file in the moduleroot.
114
+ For sanity's sake you should commit and push these changes, but in this mode the update will be rendered from the state of the files locally.
118
115
 
119
116
  #### Dry-run
120
117
 
121
- Do a dry-run to see what files will be changed, added and removed. This clones
122
- the modules to `modules/<namespace>-<modulename>` in the current working
123
- directory, or if the modules are already cloned, does an effective `git fetch
124
- origin; git checkout master; git reset --hard origin/master` on the modules.
125
- Don't run modulesync if the current working directory contains a modules/
126
- directory with changes you want to keep. The dry-run makes local changes there,
127
- but does not commit or push changes. It is still destructive in that it
128
- overwrites local changes.
118
+ Do a dry-run to see what files will be changed, added and removed.
119
+ This clones the modules to `modules/<namespace>-<modulename>` in the current working directory, or if the modules are already cloned, does an effective
129
120
 
121
+ ```shell
122
+ git fetch origin
123
+ git checkout master
124
+ git reset --hard origin/master
130
125
  ```
126
+
127
+ on the modules.
128
+ Don't run modulesync if the current working directory contains a `modules/` directory with changes you want to keep.
129
+ The dry-run makes local changes there, but does not commit or push changes.
130
+ It is still destructive in that it overwrites local changes.
131
+
132
+ ```shell
131
133
  msync update --noop
132
134
  ```
133
135
 
134
136
  #### Offline support
135
137
 
136
- The --offline flag was added to allow a user to disable git support within
137
- msync. One reason for this is because the user wants to control git commands
138
- external to msync. Note, when using this command, msync assumes you have
139
- create the folder structure and git repositories correctly. If not, msync will
140
- fail to update correctly.
138
+ The --offline flag was added to allow a user to disable git support within msync.
139
+ One reason for this is because the user wants to control git commands external to msync.
140
+ Note, when using this command, msync assumes you have create the folder structure and git repositories correctly.
141
+ If not, msync will fail to update correctly.
141
142
 
142
- ```
143
+ ```shell
143
144
  msync update --offline
144
145
  ```
145
146
 
146
147
  #### Damage mode
147
148
 
148
- Make changes for real and push them back to master. This operates on the
149
- pre-cloned modules from the dry-run or clones them fresh if the modules aren't
150
- found.
149
+ Make changes for real and push them back to master.
150
+ This operates on the pre-cloned modules from the dry-run or clones them fresh if the modules aren't found.
151
151
 
152
- ```
152
+ ```shell
153
153
  msync update -m "Commit message"
154
154
  ```
155
155
 
156
156
  Amend the commit if changes are needed.
157
157
 
158
- ```
158
+ ```shell
159
159
  msync update --amend
160
160
  ```
161
161
 
162
- For most workflows you will need to force-push an amended commit. Not required
163
- for gerrit.
162
+ For most workflows you will need to force-push an amended commit.
163
+ Not required for gerrit.
164
164
 
165
- ```
165
+ ```shell
166
166
  msync update --amend --force
167
167
  ```
168
168
 
169
+ Use `--sign` to cryptographically sign commits and tags with Git's configured signing key and backend.
170
+ ModuleSync only passes Git's signing flags (`git commit --gpg-sign` and `git tag --sign`).
171
+ It does not configure the signing key, format, or backend; users must configure these settings in their Git configuration.
172
+ Use `--signoff` to add Git's `Signed-off-by` trailer.
173
+ The options can be combined and also apply to the release commit created by `--bump`.
174
+ When combined with `--tag`, `--sign` creates signed annotated tags using `git tag --sign`.
175
+
176
+ ```shell
177
+ msync update -m "Commit message" --sign --signoff
178
+ ```
179
+
169
180
  #### Automating Updates
170
181
 
171
- You can install a pre-push git hook to automatically clone, update, and push
172
- modules upon pushing changes to the configuration directory. This does not
173
- include a noop mode.
182
+ You can install a pre-push git hook to automatically clone, update, and push modules upon pushing changes to the configuration directory.
183
+ This does not include a noop mode.
174
184
 
175
- ```
185
+ ```shell
176
186
  msync hook activate
177
187
  ```
178
188
 
179
- If you have activated the hook but want to make changes to the configuration
180
- directory (such as changes to `managed_modules.yml` or `modulesync.yml`) without
181
- touching the modules, you can deactivate the hook.
189
+ If you have activated the hook but want to make changes to the configuration directory (such as changes to `managed_modules.yml` or `modulesync.yml`) without touching the modules, you can deactivate the hook.
182
190
 
183
- ```
191
+ ```shell
184
192
  msync hook deactivate
185
193
  ```
186
194
 
187
195
  #### Submitting PRs/MRs to GitHub or GitLab
188
196
 
189
- You can have modulesync submit Pull Requests on GitHub or Merge Requests on
190
- GitLab automatically with the `--pr` CLI option.
197
+ You can have modulesync submit Pull Requests on GitHub or Merge Requests on GitLab automatically with the `--pr` CLI option.
191
198
 
192
- ```
199
+ ```shell
193
200
  msync update --pr
194
201
  ```
195
202
 
196
- In order for GitHub PRs or GitLab MRs to work, you must either provide
197
- the `GITHUB_TOKEN` or `GITLAB_TOKEN` environment variables,
198
- or set them per repository in `managed_modules.yml`, using the `github` or
199
- `gitlab` keys respectively.
203
+ In order for GitHub PRs or GitLab MRs to work, you must either provide the `GITHUB_TOKEN` or `GITLAB_TOKEN` environment variables.
204
+ You can also set them per repository in `managed_modules.yml`, using the `github` or `gitlab` keys respectively.
200
205
 
201
- For GitHub Enterprise and self-hosted GitLab instances you also need to set the
202
- `GITHUB_BASE_URL` or `GITLAB_BASE_URL` environment variables, or specify the
203
- `base_url` parameter per repository in `managed_modules.yml`:
206
+ For GitHub Enterprise and self-hosted GitLab instances you also need to set the `GITHUB_BASE_URL` or `GITLAB_BASE_URL` environment variables, or specify the `base_url` parameter in `modulesync.yml`:
204
207
 
205
208
  ```yaml
206
209
  ---
@@ -217,13 +220,9 @@ repo2:
217
220
 
218
221
  Then:
219
222
 
220
- * Set the PR/MR title with `--pr-title` or in `modulesync.yml` with the
221
- `pr_title` attribute.
222
- * Assign labels to the PR/MR with `--pr-labels` or in `modulesync.yml` with
223
- the `pr_labels` attribute. **NOTE:** `pr_labels` should be a list. When
224
- using the `--pr-labels` CLI option, you should use a comma separated list.
225
- * Set the target branch with `--pr-target-branch` or in `modulesync.yml` with
226
- the `pr_target_branch` attribute.
223
+ * Set the PR/MR title with `--pr-title` or in `modulesync.yml` with the `pr_title` attribute.
224
+ * Assign labels to the PR/MR with `--pr-labels` or in `modulesync.yml` with the `pr_labels` attribute. **NOTE:** `pr_labels` should be a list. When using the `--pr-labels` CLI option, you should use a comma separated list.
225
+ * Set the target branch with `--pr-target-branch` or in `modulesync.yml` with the `pr_target_branch` attribute.
227
226
 
228
227
  More details for GitHub:
229
228
 
@@ -231,42 +230,35 @@ More details for GitHub:
231
230
 
232
231
  ### Using Forks and Non-master branches
233
232
 
234
- The default functionality is to run ModuleSync on the puppetlabs modules, but
235
- you can use this on your own organization's modules. This functionality also
236
- applies if you want to work on a fork of the puppetlabs modules or work on a
237
- non-master branch of any organization's modules. ModuleSync does not support
238
- cloning from one remote and pushing to another, you are expected to fork
239
- manually. It does not support automating pull requests.
233
+ The default functionality is to run ModuleSync on the Vox Pupuli modules, but you can use this on your own organization's modules.
234
+ This functionality also applies if you want to work on a fork of the Vox Pupuli modules or work on a non-master branch of any organization's modules.
235
+ ModuleSync does not support cloning from one remote and pushing to another, you are expected to fork manually.
236
+ It does not support automating pull requests.
240
237
 
241
238
  #### Dry-run
242
239
 
243
- If you dry-run before doing the live update, you need to specify what namespace
244
- to clone from because the live update will not re-clone if the modules are
245
- already cloned. The namespace should be your fork, not the upstream module (if
246
- working on a fork).
240
+ If you dry-run before doing the live update, you need to specify what namespace to clone from because the live update will not re-clone if the modules are already cloned.
241
+ The namespace should be your fork, not the upstream module (if working on a fork).
247
242
 
248
- ```
249
- msync update -n puppetlabs --noop
243
+ ```shell
244
+ msync update -n voxpupuli --noop
250
245
  ```
251
246
 
252
247
  #### Damage mode
253
248
 
254
- You don't technically need to specify the namespace if the modules are already
255
- cloned from the dry-run, but it doesn't hurt. You do need to specify the
256
- namespace if the modules are not pre-cloned. You need to specify a branch to
257
- push to if you are not pushing to master.
249
+ You don't technically need to specify the namespace if the modules are already cloned from the dry-run, but it doesn't hurt.
250
+ You do need to specify the namespace if the modules are not pre-cloned.
251
+ You need to specify a branch to push to if you are not pushing to master.
258
252
 
259
- ```
260
- msync update -n puppetlabs -b sync_branch -m "Commit message"
253
+ ```shell
254
+ msync update -n voxpupuli -b sync_branch -m "Commit message"
261
255
  ```
262
256
 
263
257
  #### Configuring ModuleSync defaults
264
258
 
265
- If you're not using the puppetlabs modules or only ever pushing to a fork of
266
- them, then specifying the namespace and branch every time you use ModuleSync
267
- probably seems excessive. You can create a file called modulesync.yml in the
268
- configuration directory that provides these arguments automatically. This file
269
- has a form such as:
259
+ If you're not using Vox Pupuli modules or only ever pushing to a fork of them, then specifying the namespace and branch every time you use ModuleSync probably seems excessive.
260
+ You can create a file called modulesync.yml in the configuration directory that provides these arguments automatically.
261
+ This file has a form such as:
270
262
 
271
263
  ```yaml
272
264
  ---
@@ -276,17 +268,20 @@ branch: modulesyncbranch
276
268
 
277
269
  Then you can run ModuleSync without extra arguments:
278
270
 
279
- ```
271
+ ```shell
280
272
  msync update --noop
281
273
  msync update -m "Commit message"
282
274
  ```
283
275
 
276
+ Use `--rebase` to update the local working branch with the latest remote default
277
+ branch before ModuleSync changes any files. If the rebase conflicts, ModuleSync
278
+ aborts the rebase and reports an error for that module. A branch changed by the
279
+ rebase is pushed even when the template sync itself produces no changes.
280
+
284
281
  Available parameters for modulesync.yml
285
282
 
286
283
  * `git_base` : The default URL to git clone from (Default: 'git@github.com:')
287
- * `namespace` : Namespace of the projects to manage (Default: 'puppetlabs').
288
- This value can be overridden in the module name (e.g. 'namespace/mod') or by
289
- using the `namespace` key for the module in `managed_modules.yml`.
284
+ * `namespace` : Namespace of the projects to manage (Default: 'puppetlabs'). This value can be overridden in the module name (e.g. 'namespace/mod') or by using the `namespace` key for the module in `managed_modules.yml`.
290
285
  * `branch` : Branch to push to (Default: 'master')
291
286
  * `remote_branch` : Remote branch to push to (Default: Same value as branch)
292
287
  * `message` : Commit message to apply to updated modules.
@@ -333,14 +328,14 @@ pre_commit_script: openstack-commit-msg-hook.sh
333
328
 
334
329
  If you only want to sync some of the repositories in your managed_modules.yml, use the `-f` flag to filter by a regex:
335
330
 
336
- ```
331
+ ```shell
337
332
  msync update -f augeas -m "Commit message" # acts only on the augeas module
338
333
  msync update -f puppet-a..o "Commit message"
339
334
  ```
340
335
 
341
336
  If you want to skip syncing some of the repositories in your managed_modules.yml, use the `-x` flag to filter by a regex:
342
337
 
343
- ```
338
+ ```shell
344
339
  msync update -x augeas -m "Commit message" # acts on all modules except the augeas module
345
340
  msync update -x puppet-a..o "Commit message"
346
341
  ```
@@ -351,45 +346,47 @@ If no `-f` is specified, all repository are processed, if no `-x` is specified n
351
346
 
352
347
  If you want to push the modified branch to a different remote branch, you can use the -r flag:
353
348
 
354
- ```
349
+ ```shell
355
350
  msync update -r master_new -m "Commit message"
356
351
  ```
357
352
 
358
353
  #### Automating updates
359
354
 
360
- If you install a git hook, you need to tell it what remote and branch to push
361
- to. This may not work properly if you already have the modules cloned from a
362
- different remote. The hook will also look in modulesync.yml for default
363
- arguments.
355
+ If you install a git hook, you need to tell it what remote and branch to push to.
356
+ This may not work properly if you already have the modules cloned from a different remote.
357
+ The hook will also look in modulesync.yml for default arguments.
364
358
 
365
- ```
366
- msync hook activate -n puppetlabs -b sync_branch
359
+ ```shell
360
+ msync hook activate -n voxpupuli -b sync_branch
367
361
  ```
368
362
 
369
363
  #### Updating metadata.json
370
364
 
371
- Modulesync can optionally bump the minor version in `metadata.json` for each
372
- modified modules if you add the `--bump` flag to the command line:
365
+ Modulesync can optionally bump the minor version in `metadata.json` for each modified modules if you add the `--bump` flag to the command line:
373
366
 
374
- ```
367
+ ```shell
375
368
  msync update -m "Commit message" --bump
376
369
  ```
377
370
 
378
371
  #### Tagging repositories
379
372
 
380
- If you wish to tag the modified repositories with the newly bumped version,
381
- you can do so by using the `--tag` flag:
373
+ If you wish to tag the modified repositories with the newly bumped version, you can do so by using the `--tag` flag:
382
374
 
383
- ```
375
+ ```shell
384
376
  msync update -m "Commit message" --bump --tag
385
377
  ```
386
378
 
379
+ Add `--sign` to cryptographically sign both the release commit and tag:
380
+
381
+ ```shell
382
+ msync update -m "Commit message" --bump --tag --sign
383
+ ```
384
+
387
385
  #### Setting the tag pattern
388
386
 
389
- You can also set the format of the tag to be used (`printf`-formatted)
390
- by setting the `tag_pattern` option:
387
+ You can also set the format of the tag to be used (`printf`-formatted) by setting the `tag_pattern` option:
391
388
 
392
- ```
389
+ ```shell
393
390
  msync update -m "Commit message" --bump --tag --tag_pattern 'v%s'
394
391
  ```
395
392
 
@@ -399,30 +396,28 @@ The default for the tag pattern is `%s`.
399
396
 
400
397
  By default `msync execute` will remove bundler related env vars (^BUNDLE|^SOURCE_DATE_EPOCH$|^GEM_|RUBY), you can use `--env` flag to keep specified env vars:
401
398
 
402
- ```
399
+ ```shell
403
400
  msync execute --env BUNDLE_PATH,BUNDLE_GEMFILE bundle exec rake lint
404
401
  ```
405
402
 
406
403
  #### Updating the CHANGELOG
407
404
 
408
- When bumping the version in `metadata.json`, modulesync can let you
409
- updating `CHANGELOG.md` in each modified repository.
405
+ When bumping the version in `metadata.json`, modulesync can let you updating `CHANGELOG.md` in each modified repository.
410
406
 
411
407
  This is one by using the `--changelog` flag:
412
408
 
413
- ```
409
+ ```shell
414
410
  msync update -m "Commit message" --bump --changelog
415
411
  ```
416
412
 
417
- This flag will cause the `CHANGELOG.md` file to be updated with the
418
- current date, bumped (minor) version, and commit message.
413
+ This flag will cause the `CHANGELOG.md` file to be updated with the current date, bumped (minor) version, and commit message.
419
414
 
420
415
  If `CHANGELOG.md` is absent in the repository, nothing will happen.
421
416
 
422
-
423
417
  #### Working with templates
424
418
 
425
- As mentioned, files in the moduleroot directory must be ERB templates (they must have an .erb extension, or they will be ignored). These files have direct access to @configs hash, which gets values from config_defaults.yml file and from the module being processed:
419
+ As mentioned, files in the moduleroot directory must be ERB templates (they must have an .erb extension, or they will be ignored).
420
+ These files have direct access to @configs hash, which gets values from config_defaults.yml file and from the module being processed:
426
421
 
427
422
  ```erb
428
423
  <%= @configs[:git_base] %>
@@ -430,8 +425,8 @@ As mentioned, files in the moduleroot directory must be ERB templates (they must
430
425
  <%= @configs[:puppet_module] %>
431
426
  ```
432
427
 
433
- Alternatively some meta data is passed to the template. This will allow you to add custom Ruby extensions inside the
434
- template, reading other files from the module, to make the template system more adaptive.
428
+ Alternatively some meta data is passed to the template.
429
+ This will allow you to add custom Ruby extensions inside the template, reading other files from the module, to make the template system more adaptive.
435
430
 
436
431
  ```erb
437
432
  module: <%= @metadata[:module_name] %>
@@ -441,16 +436,15 @@ workdir: <%= @metadata[:workdir] %>
441
436
 
442
437
  Will result in something like:
443
438
 
444
- ```
439
+ ```yaml
445
440
  module: puppet-test
446
441
  target: modules/github-org/puppet-test/test
447
442
  workdir: modules/github-org/puppet-test
448
443
  ```
449
444
 
450
- The Templates
451
- -------------
445
+ ## The Templates
452
446
 
453
- See [Vox Pupuli's modulesync\_config](https://github.com/voxpupuli/modulesync_config) and [Puppet's modulesync\_configs (Archived)](https://github.com/puppetlabs/modulesync_configs) repositories for different templates currently in use.
447
+ See [Vox Pupuli's modulesync\_config](https://github.com/voxpupuli/modulesync_config) repository for templates currently in use.
454
448
 
455
449
  ## Transfer Notice
456
450
 
@@ -467,6 +461,7 @@ This gem is licensed under the Apache-2 license.
467
461
  ## Release information
468
462
 
469
463
  To make a new release, please do:
464
+
470
465
  * update the version in `lib/modulesync/version.rb`
471
466
  * Install gems with `bundle install --with release --path .vendor`
472
467
  * generate the changelog with `bundle exec rake changelog`
data/features/cli.feature CHANGED
@@ -28,6 +28,11 @@ Feature: CLI
28
28
  msync clone
29
29
  """
30
30
 
31
+ Scenario: Update help lists commit signing options
32
+ When I successfully run `msync update --help`
33
+ Then the output should match /--sign/
34
+ And the output should match /--signoff/
35
+
31
36
  Scenario: When overriding a setting from the config file on the command line
32
37
  Given a puppet module "puppet-test" from "fakenamespace"
33
38
  And a file named "managed_modules.yml" with:
@@ -88,6 +88,14 @@ module ModuleSync
88
88
  type: :boolean,
89
89
  desc: 'Amend previous commit',
90
90
  default: false
91
+ option :sign,
92
+ type: :boolean,
93
+ desc: 'Cryptographically sign commits and tags',
94
+ default: false
95
+ option :signoff,
96
+ type: :boolean,
97
+ desc: 'Add a Signed-off-by trailer to commits',
98
+ default: false
91
99
  option :force,
92
100
  type: :boolean,
93
101
  desc: 'Force push amended commit',
@@ -114,6 +122,10 @@ module ModuleSync
114
122
  type: :boolean,
115
123
  desc: 'Do not run any Git commands. Allows the user to manage Git outside of ModuleSync.',
116
124
  default: false
125
+ option :rebase,
126
+ type: :boolean,
127
+ desc: 'Rebase the local branch onto the latest remote default branch before syncing.',
128
+ default: false
117
129
  option :bump,
118
130
  type: :boolean,
119
131
  desc: 'Bump module version to the next minor',
@@ -41,8 +41,17 @@ module ModuleSync
41
41
  base: target_branch,
42
42
  head: head)
43
43
  unless pull_requests.empty?
44
- # Skip creating the PR if it exists already.
45
- $stdout.puts "Skipped! #{pull_requests.length} PRs found for branch '#{source_branch}'"
44
+ # assumption: there should only be one PR for a given source and target branch, so we just take the first one
45
+ pr = pull_requests.first
46
+
47
+ if pr['title'] == title
48
+ $stdout.puts "Skipped! #{pull_requests.length} PRs found for branch '#{source_branch}'"
49
+ $stdout.puts "Skipped! Existing PR ##{pr['number']} already has title '#{title}'"
50
+ else
51
+ @api.update_pull_request(repo_path, pr['number'], title: title)
52
+ $stdout.puts "Updated title of existing PR ##{pr['number']} to '#{title}'"
53
+ end
54
+
46
55
  return
47
56
  end
48
57
 
@@ -40,8 +40,17 @@ module ModuleSync
40
40
  source_branch: source_branch,
41
41
  target_branch: target_branch)
42
42
  unless merge_requests.empty?
43
- # Skip creating the MR if it exists already.
44
- $stdout.puts "Skipped! #{merge_requests.length} MRs found for branch '#{source_branch}'"
43
+ # assumption: there should only be one MR for a given source and target branch, so we just take the first one
44
+ mr = merge_requests.first
45
+
46
+ if mr['title'] == title
47
+ $stdout.puts "Skipped! #{merge_requests.length} MRs found for branch '#{source_branch}'"
48
+ $stdout.puts "Skipped! Existing MR !#{mr['iid']} already has title '#{title}'"
49
+ else
50
+ @api.update_merge_request(repo_path, mr['iid'], title: title)
51
+ $stdout.puts "Updated title of existing MR !#{mr['iid']} to '#{title}'"
52
+ end
53
+
45
54
  return
46
55
  end
47
56
 
@@ -25,13 +25,13 @@ module ModuleSync
25
25
  end
26
26
  end
27
27
 
28
- def bump(message, changelog = false)
28
+ def bump(message, changelog = false, signing_options = {})
29
29
  m = Blacksmith::Modulefile.new path('metadata.json')
30
30
  new = m.bump!
31
31
  puts "Bumped to version #{new}"
32
32
  repository.git.add('metadata.json')
33
33
  update_changelog(new, message) if changelog
34
- repository.git.commit("Release version #{new}")
34
+ repository.commit_changes("Release version #{new}", signing_options)
35
35
  repository.git.push
36
36
  new
37
37
  end
@@ -42,6 +42,12 @@ module ModuleSync
42
42
  commits.any?
43
43
  end
44
44
 
45
+ def branch_behind?(branch, target_branch)
46
+ log = repo.log(1).between(branch, "origin/#{target_branch}")
47
+ commits = log.respond_to?(:execute) ? log.execute : log
48
+ commits.any?
49
+ end
50
+
45
51
  def default_branch
46
52
  # `Git.default_branch` requires ruby-git >= 1.17.0
47
53
  return Git.default_branch(repo.dir) if Git.respond_to? :default_branch
@@ -52,6 +58,12 @@ module ModuleSync
52
58
  %r{remotes/origin/HEAD\s+->\s+origin/(?<branch>.+?)$}.match(symbolic_ref.full)[:branch]
53
59
  end
54
60
 
61
+ def remote_default_branch
62
+ return Git.default_branch(@remote) if Git.respond_to? :default_branch
63
+
64
+ default_branch
65
+ end
66
+
55
67
  def switch(branch:)
56
68
  unless branch
57
69
  branch = default_branch
@@ -87,13 +99,16 @@ module ModuleSync
87
99
  @git = Git.clone(@remote, @directory)
88
100
  end
89
101
 
90
- def prepare_workspace(branch:, operate_offline:)
102
+ def prepare_workspace(branch:, operate_offline:, rebase: false)
103
+ @rebased = false
91
104
  if cloned?
92
105
  puts "Overriding any local changes to repository in '#{@directory}'"
93
106
  git.fetch 'origin', prune: true unless operate_offline
94
107
  git.reset_hard
108
+ rebase_target = remote_default_branch if rebase && !operate_offline
95
109
  switch(branch: branch)
96
110
  git.pull('origin', branch) if !operate_offline && remote_branch_exists?(branch)
111
+ rebase_onto(rebase_target) if rebase_target
97
112
  else
98
113
  raise ModuleSync::Error, 'Unable to clone in offline mode.' if operate_offline
99
114
 
@@ -102,6 +117,31 @@ module ModuleSync
102
117
  end
103
118
  end
104
119
 
120
+ def rebase_onto(branch)
121
+ return false if repo.current_branch == branch || !branch_behind?(repo.current_branch, branch)
122
+
123
+ puts "Rebasing #{repo.current_branch} onto origin/#{branch}"
124
+ repo.lib.send(:command, 'rebase', "origin/#{branch}")
125
+ @rebased = true
126
+ rescue Git::Error => e
127
+ begin
128
+ repo.lib.send(:command, 'rebase', '--abort')
129
+ rescue Git::Error
130
+ # Preserve the original rebase error if Git had no rebase to abort.
131
+ end
132
+ raise ModuleSync::Error, "Rebase onto origin/#{branch} failed and was aborted: #{e.message}"
133
+ end
134
+
135
+ def push_changes(branch, remote_branch, options)
136
+ refspec = remote_branch ? "#{branch}:#{remote_branch}" : branch
137
+ if @rebased && !options[:force]
138
+ repo.lib.send(:command, 'push', '--force-with-lease', 'origin', refspec)
139
+ else
140
+ opts_push = options[:force] ? { force: true } : {}
141
+ repo.push('origin', refspec, opts_push)
142
+ end
143
+ end
144
+
105
145
  def default_reset_branch(branch)
106
146
  remote_branch_exists?(branch) ? branch : default_branch
107
147
  end
@@ -125,10 +165,10 @@ module ModuleSync
125
165
  end
126
166
  end
127
167
 
128
- def tag(version, tag_pattern)
168
+ def tag(version, tag_pattern, sign: false)
129
169
  tag = tag_pattern % version
130
170
  puts "Tagging with #{tag}"
131
- repo.add_tag(tag)
171
+ repo.add_tag(tag, sign: sign)
132
172
  repo.push('origin', tag)
133
173
  end
134
174
 
@@ -138,6 +178,19 @@ module ModuleSync
138
178
  selected_branch
139
179
  end
140
180
 
181
+ def commit_changes(message, options)
182
+ commit_options = {}
183
+ commit_options[:amend] = true if options[:amend]
184
+ commit_options[:gpg_sign] = true if options[:sign]
185
+ return repo.commit(message, commit_options) unless options[:signoff]
186
+
187
+ arguments = ["--message=#{message}"]
188
+ arguments.push('--amend', '--no-edit') if options[:amend]
189
+ arguments << '--gpg-sign' if options[:sign]
190
+ arguments << '--signoff'
191
+ repo.lib.send(:command, 'commit', *arguments)
192
+ end
193
+
141
194
  # Git add/rm, git commit, git push
142
195
  def submit_changes(files, options)
143
196
  message = options[:message]
@@ -150,27 +203,23 @@ module ModuleSync
150
203
  end
151
204
  end
152
205
  begin
153
- opts_commit = {}
154
- opts_push = {}
155
- opts_commit = { amend: true } if options[:amend]
156
- opts_push = { force: true } if options[:force]
157
206
  if options[:pre_commit_script]
158
207
  script = "#{File.dirname(__FILE__, 3)}/contrib/#{options[:pre_commit_script]}"
159
208
  system(script, @directory)
160
209
  end
161
210
  if repo.status.changed.empty? && repo.status.added.empty? && repo.status.deleted.empty?
162
211
  puts "There were no changes in '#{@directory}'. Not committing."
163
- return false
212
+ return false unless @rebased
164
213
  else
165
- repo.commit(message, opts_commit)
214
+ commit_changes(message, options)
166
215
  end
167
216
  if options[:remote_branch]
168
- if remote_branch_differ?(branch, options[:remote_branch])
169
- repo.push('origin', "#{branch}:#{options[:remote_branch]}", opts_push)
217
+ if @rebased || remote_branch_differ?(branch, options[:remote_branch])
218
+ push_changes(branch, options[:remote_branch], options)
170
219
  puts "Changes have been pushed to: '#{branch}:#{options[:remote_branch]}'"
171
220
  end
172
221
  else
173
- repo.push('origin', branch, opts_push)
222
+ push_changes(branch, nil, options)
174
223
  puts "Changes have been pushed to: '#{branch}'"
175
224
  end
176
225
  rescue Git::Error => e
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ModuleSync
4
- VERSION = '4.4.0'
4
+ VERSION = '4.6.0'
5
5
  end
data/lib/modulesync.rb CHANGED
@@ -123,7 +123,8 @@ module ModuleSync
123
123
  # but we totally skip the workspace preparation to keep the current behavior
124
124
  unless options[:offline]
125
125
  puppet_module.repository.prepare_workspace(branch: options[:branch],
126
- operate_offline: false)
126
+ operate_offline: false,
127
+ rebase: options[:rebase])
127
128
  end
128
129
 
129
130
  module_configs = Util.parse_config puppet_module.path(MODULE_CONF_FILE)
@@ -150,8 +151,9 @@ module ModuleSync
150
151
  pushed = puppet_module.repository.submit_changes(files_to_manage, options)
151
152
  # Only bump/tag if pushing didn't fail (i.e. there were changes)
152
153
  if pushed && options[:bump]
153
- new = puppet_module.bump(options[:message], options[:changelog])
154
- puppet_module.repository.tag(new, options[:tag_pattern]) if options[:tag]
154
+ signing_options = { sign: options[:sign], signoff: options[:signoff] }
155
+ new = puppet_module.bump(options[:message], options[:changelog], signing_options)
156
+ puppet_module.repository.tag(new, options[:tag_pattern], sign: options[:sign]) if options[:tag]
155
157
  end
156
158
  options[:pr] && (pushed || puppet_module.pull_request_branch_ready?) && puppet_module.open_pull_request
157
159
  end
@@ -54,7 +54,7 @@ describe ModuleSync::GitService::GitHub do
54
54
  expect { @it.open_pull_request(**args) }.to output(/Submitted PR/).to_stdout
55
55
  end
56
56
 
57
- it 'skips submitting PR if one has already been issued' do
57
+ it 'updates the title if an existing PR has a different title' do
58
58
  pr = {
59
59
  'title' => 'Test title',
60
60
  'html_url' => 'https://example.com/pulls/44',
@@ -66,7 +66,28 @@ describe ModuleSync::GitService::GitHub do
66
66
  state: 'open',
67
67
  base: 'master',
68
68
  head: "#{args[:namespace]}:#{args[:source_branch]}").and_return([pr])
69
- expect { @it.open_pull_request(**args) }.to output("Skipped! 1 PRs found for branch 'test'\n").to_stdout
69
+ expect(@client).to receive(:update_pull_request)
70
+ .with(args[:repo_path], pr['number'], title: args[:title])
71
+ expect { @it.open_pull_request(**args) }
72
+ .to output("Updated title of existing PR #44 to 'Test PR is submitted'\n").to_stdout
73
+ end
74
+
75
+ it 'skips updating an existing PR if its title is unchanged' do
76
+ pr = {
77
+ 'title' => args[:title],
78
+ 'html_url' => 'https://example.com/pulls/44',
79
+ 'number' => '44',
80
+ }
81
+
82
+ expect(@client).to receive(:pull_requests)
83
+ .with(args[:repo_path],
84
+ state: 'open',
85
+ base: 'master',
86
+ head: "#{args[:namespace]}:#{args[:source_branch]}").and_return([pr])
87
+ expect(@client).not_to receive(:update_pull_request)
88
+ expect { @it.open_pull_request(**args) }
89
+ .to output("Skipped! 1 PRs found for branch 'test'\n" \
90
+ "Skipped! Existing PR #44 already has title 'Test PR is submitted'\n").to_stdout
70
91
  end
71
92
 
72
93
  context 'when labels are set' do
@@ -44,7 +44,7 @@ describe ModuleSync::GitService::GitLab do
44
44
  expect { @it.open_pull_request(**args) }.to output(/Submitted MR/).to_stdout
45
45
  end
46
46
 
47
- it 'skips submitting MR if one has already been issued' do
47
+ it 'updates the title if an existing MR has a different title' do
48
48
  mr = {
49
49
  'title' => 'Test title',
50
50
  'html_url' => 'https://example.com/pulls/44',
@@ -56,8 +56,28 @@ describe ModuleSync::GitService::GitLab do
56
56
  state: 'opened',
57
57
  source_branch: args[:source_branch],
58
58
  target_branch: 'master').and_return([mr])
59
+ expect(@client).to receive(:update_merge_request)
60
+ .with(args[:repo_path], mr['iid'], title: args[:title])
61
+ expect { @it.open_pull_request(**args) }
62
+ .to output("Updated title of existing MR !44 to 'Test MR is submitted'\n").to_stdout
63
+ end
64
+
65
+ it 'skips updating an existing MR if its title is unchanged' do
66
+ mr = {
67
+ 'title' => args[:title],
68
+ 'html_url' => 'https://example.com/pulls/44',
69
+ 'iid' => '44',
70
+ }
59
71
 
60
- expect { @it.open_pull_request(**args) }.to output("Skipped! 1 MRs found for branch 'test'\n").to_stdout
72
+ expect(@client).to receive(:merge_requests)
73
+ .with(args[:repo_path],
74
+ state: 'opened',
75
+ source_branch: args[:source_branch],
76
+ target_branch: 'master').and_return([mr])
77
+ expect(@client).not_to receive(:update_merge_request)
78
+ expect { @it.open_pull_request(**args) }
79
+ .to output("Skipped! 1 MRs found for branch 'test'\n" \
80
+ "Skipped! Existing MR !44 already has title 'Test MR is submitted'\n").to_stdout
61
81
  end
62
82
 
63
83
  context 'when labels are set' do
@@ -15,10 +15,12 @@ describe ModuleSync::Repository do
15
15
  end
16
16
  let(:branches) { instance_double(Git::Branches, remote: remote_branches) }
17
17
  let(:log) { instance_double(Git::Log) }
18
- let(:git) { instance_double(Git::Base, branches: branches, log: log) }
18
+ let(:git_lib) { instance_double(Git::Lib) }
19
+ let(:git) { instance_double(Git::Base, branches: branches, lib: git_lib, log: log) }
19
20
 
20
21
  before do
21
22
  repository.instance_variable_set(:@git, git)
23
+ ModuleSync.instance_variable_set(:@options, verbose: false)
22
24
  end
23
25
 
24
26
  it 'detects commits on a remote source branch that are missing from the target branch' do
@@ -33,4 +35,116 @@ describe ModuleSync::Repository do
33
35
 
34
36
  expect(repository.remote_branch_ahead?('modulesync', 'main')).to be false
35
37
  end
38
+
39
+ describe '#rebase_onto' do
40
+ before do
41
+ allow(git).to receive(:current_branch).and_return('modulesync')
42
+ allow(log).to receive(:between).with('modulesync', 'origin/main').and_return(log)
43
+ allow(log).to receive(:execute).and_return([instance_double(Git::Object::Commit)])
44
+ end
45
+
46
+ it 'rebases the current branch onto the remote default branch' do
47
+ expect(git_lib).to receive(:send).with(:command, 'rebase', 'origin/main')
48
+
49
+ repository.rebase_onto('main')
50
+ end
51
+
52
+ it 'does not rebase the default branch onto itself' do
53
+ allow(git).to receive(:current_branch).and_return('main')
54
+ expect(git_lib).not_to receive(:send)
55
+
56
+ repository.rebase_onto('main')
57
+ end
58
+
59
+ it 'does not rebase a branch that already contains the remote default branch' do
60
+ allow(log).to receive(:execute).and_return([])
61
+ expect(git_lib).not_to receive(:send)
62
+
63
+ repository.rebase_onto('main')
64
+ end
65
+
66
+ it 'aborts and reports a failed rebase' do
67
+ allow(git_lib).to receive(:send).with(:command, 'rebase', 'origin/main').and_raise(Git::Error, 'merge conflict')
68
+ expect(git_lib).to receive(:send).with(:command, 'rebase', '--abort')
69
+
70
+ expect { repository.rebase_onto('main') }
71
+ .to raise_error(ModuleSync::Error, %r{Rebase onto origin/main failed and was aborted: merge conflict})
72
+ end
73
+ end
74
+
75
+ describe '#prepare_workspace' do
76
+ it 'fetches and rebases the selected branch onto the remote default branch when requested' do
77
+ allow(Dir).to receive(:exist?).with('/tmp/example/.git').and_return(true)
78
+ allow(Git).to receive(:default_branch).with('https://github.com/example/repository.git').and_return('main')
79
+ allow(git).to receive(:current_branch).and_return('modulesync')
80
+ allow(log).to receive(:between).with('modulesync', 'origin/main').and_return(log)
81
+ allow(log).to receive(:execute).and_return([instance_double(Git::Object::Commit)])
82
+ expect(git).to receive(:fetch).with('origin', prune: true).ordered
83
+ expect(git).to receive(:reset_hard).ordered
84
+ expect(git).to receive(:pull).with('origin', 'modulesync').ordered
85
+ expect(git_lib).to receive(:send).with(:command, 'rebase', 'origin/main').ordered
86
+
87
+ repository.prepare_workspace(branch: 'modulesync', operate_offline: false, rebase: true)
88
+ end
89
+ end
90
+
91
+ describe '#submit_changes' do
92
+ it 'does not push an unchanged branch that was not rebased' do
93
+ status = instance_double(Git::Status, added: {}, changed: {}, deleted: {})
94
+ branch = instance_double(Git::Branch, checkout: true)
95
+ allow(git).to receive(:branch).with('modulesync').and_return(branch)
96
+ allow(git).to receive(:status).and_return(status)
97
+ expect(git).not_to receive(:push)
98
+ expect(git_lib).not_to receive(:send)
99
+
100
+ result = repository.submit_changes([], branch: 'modulesync', message: 'Update', force: false)
101
+
102
+ expect(result).to be false
103
+ end
104
+
105
+ it 'pushes a rebased branch even when ModuleSync made no file changes' do
106
+ status = instance_double(Git::Status, added: {}, changed: {}, deleted: {})
107
+ branch = instance_double(Git::Branch, checkout: true)
108
+ repository.instance_variable_set(:@rebased, true)
109
+ allow(git).to receive(:branch).with('modulesync').and_return(branch)
110
+ allow(git).to receive(:status).and_return(status)
111
+ expect(git_lib).to receive(:send)
112
+ .with(:command, 'push', '--force-with-lease', 'origin', 'modulesync')
113
+
114
+ result = repository.submit_changes([], branch: 'modulesync', message: 'Update', force: false)
115
+
116
+ expect(result).to be true
117
+ end
118
+ end
119
+
120
+ describe '#commit_changes' do
121
+ it 'signs the commit when requested' do
122
+ expect(git).to receive(:commit).with('Update', { gpg_sign: true })
123
+
124
+ repository.commit_changes('Update', sign: true, signoff: false)
125
+ end
126
+
127
+ it 'uses the Git signoff option when requested' do
128
+ expect(git_lib).to receive(:send)
129
+ .with(:command, 'commit', '--message=Update', '--signoff')
130
+
131
+ repository.commit_changes('Update', sign: false, signoff: true)
132
+ end
133
+
134
+ it 'combines amend, signing, and signoff options' do
135
+ expect(git_lib).to receive(:send)
136
+ .with(:command, 'commit', '--message=Update', '--amend', '--no-edit', '--gpg-sign', '--signoff')
137
+
138
+ repository.commit_changes('Update', amend: true, sign: true, signoff: true)
139
+ end
140
+ end
141
+
142
+ describe '#tag' do
143
+ it 'signs and pushes the tag when requested' do
144
+ expect(git).to receive(:add_tag).with('v1.2.3', sign: true)
145
+ expect(git).to receive(:push).with('origin', 'v1.2.3')
146
+
147
+ repository.tag('1.2.3', 'v%s', sign: true)
148
+ end
149
+ end
36
150
  end
@@ -63,5 +63,24 @@ describe ModuleSync do
63
63
 
64
64
  described_class.manage_module(puppet_module, [], {})
65
65
  end
66
+
67
+ it 'requests a rebase before syncing when enabled' do
68
+ described_class.instance_variable_set(:@options, described_class.config_defaults.merge(rebase: true))
69
+ expect(repository).to receive(:prepare_workspace).with(branch: nil, operate_offline: false, rebase: true)
70
+
71
+ described_class.manage_module(puppet_module, [], {})
72
+ end
73
+
74
+ it 'passes signing options to a release commit and tag' do
75
+ options = described_class.config_defaults.merge(message: 'Update', bump: true, tag: true, sign: true, signoff: true)
76
+ described_class.instance_variable_set(:@options, options)
77
+ allow(repository).to receive(:submit_changes).and_return(true)
78
+ expect(puppet_module).to receive(:bump)
79
+ .with('Update', nil, { sign: true, signoff: true })
80
+ .and_return('1.2.3')
81
+ expect(repository).to receive(:tag).with('1.2.3', '%s', sign: true)
82
+
83
+ described_class.manage_module(puppet_module, [], {})
84
+ end
66
85
  end
67
86
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modulesync
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.4.0
4
+ version: 4.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vox Pupuli
@@ -266,7 +266,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
266
266
  - !ruby/object:Gem::Version
267
267
  version: '0'
268
268
  requirements: []
269
- rubygems_version: 4.0.10
269
+ rubygems_version: 4.0.16
270
270
  specification_version: 4
271
271
  summary: Puppet Module Synchronizer
272
272
  test_files: []