git-maintain 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0c79a790fc66646271a7722a583509c1ce9d5c69dd1e69d0762b09fb5a5c19ab
4
+ data.tar.gz: 2d3a81431bbd6d4394f3eaa17c524b8b3ff1b7f752da10f165d5fadaa41b1c4f
5
+ SHA512:
6
+ metadata.gz: ab558a35d36b5c767590b1dabe14bcedff7a51e7d74deb83ba142a543332450e14aa357099d9444dd83fd54c8d793229de39f4f260dcc3b0d2490abc9413bfab
7
+ data.tar.gz: 3b1d4a0166512d02d48673b676fbe924a364d90b01c5e343a8fa65ae412db910b9634fd0b947367e4bfe97cf3d5f28ea7ddce3863e79b5e2b7a7530a0ec345e2
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2018 SUSE
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,194 @@
1
+ git-maintain is a simple ruby script to deal with all the hassle of maintaining stable branches in a project.
2
+
3
+ The idea is to script most of the maintenance tasks so the maintainer can focus on just reviewing and not on writing up release notes, looking for commits and such.
4
+
5
+ - Note: the workflow is highly inspired to the git-topic-branches (https://github.com/nmorey/git-topic-branches)
6
+ and both can be used at the same time
7
+ - Note2: Releasing on github is done through the git-release (https://github.com/mpalmer/github-release)
8
+
9
+ # Command summary
10
+
11
+ - **cp**: Backport commits and eventually push them to github
12
+ - **steal**: Steal commit from upstream that fixes commit in the branch or were tagged as stable
13
+ - **list**: List commit present in the branch but not in the stable branch
14
+ - **merge**: Merge branch with suffix specified in -m <suff> into the main branch
15
+ - **push**: Push branches to github for validation
16
+ - **monitor**: Check the travis state of all branches
17
+ - **push_stable**: Push to stable repo
18
+ - **monitor_stable**: Check the travis state of all stable branches
19
+ - **release**: Create new release on all concerned branches
20
+ - **reset**: Reset branch against upstream
21
+ - **submit_release**: Push the to stable and create the release packages
22
+
23
+ # Configuration
24
+
25
+ ## Basic shell setup
26
+ - git-maintain should be in your path
27
+ - Load git-maintain-completion.sh for shell completion
28
+
29
+ ## Remote setup
30
+ - the 'github' remote should be your own WIP github to test out branches before submitting to the official repo
31
+ - the 'origin' remote should be the official repo in read-only mode to avoid any accidental pushes
32
+ - the 'stable' remote should be the official repo in RW mode
33
+
34
+ ## Stealing commits
35
+
36
+ The steal feature uses was shamelessly copied it from https://git.kernel.org/pub/scm/linux/kernel/git/sashal/stable-tools.git/tree/
37
+
38
+ ## Making releases
39
+ The release process being very specific to each project, the release command does nothing by default.
40
+ However the behaviour can be overriden for specific repo (detected by repo name)
41
+
42
+ Check the lib/addons/RDMACore.rb for an example.
43
+ In this case, the release command bump the version in all the appropriate files (after computing the previous and next version numbers), commit and tags the commit.
44
+
45
+
46
+ # How do I use it
47
+
48
+ ## Branch setup
49
+ As said, this uses the branching schemes recommended/compatible of git-topic-branches.
50
+
51
+ A non compulsory recommendation is to create
52
+ I personnaly uses this scheme for my rdma-core work:
53
+ ```
54
+ $ git branch
55
+ dev/stable-v15/master
56
+ dev/stable-v15/pending
57
+ dev/stable-v16/master
58
+ dev/stable-v16/pending
59
+ dev/stable-v17/master
60
+ dev/stable-v17/pending
61
+ dev/stable-v17/test
62
+ dev/stable-v18/master
63
+ dev/stable-v18/pending
64
+ * dev/stable-v19/master
65
+ ```
66
+
67
+ I also use the git-topic-branches features:
68
+ ```
69
+ git config --get-regexp devel-base
70
+ devel-base.dev---stable-v19 origin/stable-v19
71
+ devel-base.dev---stable-v18 origin/stable-v18
72
+ devel-base.dev---stable-v17 origin/stable-v17
73
+ devel-base.dev---stable-v16 origin/stable-v16
74
+ devel-base.dev---stable-v15 origin/stable-v15
75
+ ```
76
+ This allows me to:
77
+ - autorebase on the stable branches. This is useful for dealing with development branches for stable branches
78
+ - Detect when all my patches have made it to the upstream repo
79
+
80
+ I also set the right config value for the 'steal' command to work
81
+ ```
82
+ $ git config --get-regexp stable-base
83
+ stable-base.dev---stable-v19 v19
84
+ stable-base.dev---stable-v18 v18
85
+ stable-base.dev---stable-v17 v17
86
+ stable-base.dev---stable-v16 v16
87
+ stable-base.dev---stable-v15 v15
88
+ ```
89
+ This will allow 'git maintain steal' to figure out what should be backported in the stable branches.
90
+
91
+ ## Day-to-day workflow
92
+
93
+ Watch the mailing-lists (and/or github and/or the upstream branches) for patches that are tagged for maintainance.
94
+ Apply them to the appropriate branches
95
+
96
+ ```git maintain cp -s deadbeef --version '1[789]'```
97
+
98
+ And push them to my own github repo so that Travis will check everything out
99
+
100
+ ```git maintain push --version '1[789]'```
101
+
102
+ Some time later, check their status
103
+
104
+ ```git maintain monitor --version '1[789]'```
105
+
106
+ If everything looks good, push to the stable repo
107
+
108
+ ```git maintain push_stable --version '1[789]'```
109
+
110
+ If patches have been sent to the ML but are not yet accepted, I usually try them out on a "pending" branch.
111
+
112
+ ```git maintain cp -s deadbeef --version '1[789]' -b pending```
113
+
114
+ Note that it is your own job to create the branches (yet!)
115
+
116
+ Push it to my own github too.
117
+
118
+ ```git maintain push --version '1[789]' -b pending```
119
+
120
+ Once this gets accepted (and Travis is OK too), I merge this branch back to my 'master'
121
+
122
+ ```git maintain merge --version '1[789]' -m pending```
123
+
124
+ The default -b option here is master so it is not required to specify it. Also branch suffixed with something else than master cannot be pushed to stable branches for safety reasons.
125
+
126
+ If this is all broken and the patch should not be applied, I simply reset my branch
127
+
128
+ ```git maintain reset --version '1[789]' -m pending```
129
+
130
+ Note: This has been made as safe as possible and is querying you before doing anything destructive.
131
+
132
+ ## Stealing commits
133
+
134
+ This uses a ruby version of the 'steal-stable-commits' script originally available here https://git.kernel.org/pub/scm/linux/kernel/git/sashal/stable-tools.git/tree/
135
+
136
+ It allows to automatically cherry-pick commits that have been marked as fixing a bug in your branch.
137
+
138
+ What it does is parse the master branch for commits that contains the standard
139
+
140
+ ```Fixes: deadbeef00 ("broken commit msg")```
141
+
142
+ It will then check if the broken commit is in your branch (and not already fixed). It will then prompt you for reviewing the fix and apply it on your local stable branch.
143
+
144
+ If the developers from your project follow this, it removes a lot of the hassle of finding which patch applies to which branches, It will do it for you !
145
+
146
+ You can get more infos on how all this works here: https://git.kernel.org/pub/scm/linux/kernel/git/sashal/stable-tools.git/tree/README (stable steal-commits section).
147
+
148
+ One of the added feature is the ability to blacklist a commit for certain branch.
149
+ (Useful when there is a commit broken in your stable branch but the fix breaks the ABI or simply won't apply.)
150
+
151
+ Blacklisting the fix (for a specific branch) will prevent it from ever popping up when stealing commits.
152
+ Blacklisting is done through git-notes. It attaches a note to the "fix" commit in master and simply add the name of the branch it is blacklisted in. To run-blacklist the commit, edit the note and remove the branch name.
153
+
154
+ Note that blacklisted commites will show an info message when skipped so you don't have to dig through all the notes to find which one you wanted.
155
+
156
+ To run:
157
+
158
+ ```git maintain steal```
159
+
160
+
161
+ ## Releases
162
+
163
+ Once some work has been done, it is time for a new release.
164
+
165
+ First, unless you're working for the rdma-core repo, you'll need to add your own 'add-on' class to do automatize tyour release process. Please look at 'addons/RDMACore.rb' for an example.
166
+
167
+ Then all you need to do is create your release(s)
168
+
169
+ ```git maintain release --version '1[789]'```
170
+
171
+ This will run your addon code. What you usually want to do in there is create a tag and eventually bump version numbers, add releases notes, etc.
172
+
173
+ I strongly advise here to then use the 'push_stable' command. It will update the branches, but NOT push the tag.
174
+ This means that if something has been broken by the release commit (if any), there is still time to fix it.
175
+
176
+ The tag will not have been propagated anywhere else and can be deleted manually.
177
+
178
+ ```git maintain push_stable --version '1[789]'```
179
+
180
+ You can then monitor the status on Travis
181
+
182
+ ```git maintain monitor_stable --version '1[789]'```
183
+
184
+
185
+ Once everything is green, it is time to submit your release
186
+
187
+ ```git maintain submit_release --version '1[789]'```
188
+
189
+ This will submit the all pending tags to github, and prepare an email for the project mailing list (requires the 'patch.target' option from git-topic-branches)
190
+
191
+ Review your email, send it and your day is over !
192
+
193
+ Enjoy, and feel free to report bugs, missing features and/or send patches
194
+
data/bin/git-maintain ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'optparse'
4
+ require 'net/http'
5
+ require 'json'
6
+
7
+ BACKPORT_LIB_DIR = File.dirname(__FILE__) + '/../lib'
8
+ $LOAD_PATH.push(BACKPORT_LIB_DIR)
9
+ require 'common'
10
+ $LOAD_PATH.pop()
11
+
12
+ opts = {
13
+ :br_suff => "master",
14
+ :no => false,
15
+ }
16
+
17
+ actionParser = OptionParser.new(nil, 60)
18
+ actionParser.banner = "Usage: #{__FILE__} <action> [action options]"
19
+ actionParser.separator ""
20
+ actionParser.separator "Options:"
21
+ actionParser.on("-h", "--help", "Display usage.") { |val| puts actionParser.to_s; exit 0 }
22
+ actionParser.separator "Possible actions:"
23
+ GitMaintain::getActionAttr("ACTION_HELP").each(){|x|
24
+ actionParser.separator "\t " + x
25
+ }
26
+ rest = actionParser.order!(ARGV);
27
+ if rest.length <= 0 then
28
+ STDERR.puts("Error: No action provided")
29
+ puts actionParser.to_s()
30
+ exit 1
31
+ end
32
+
33
+ action_s = ARGV[0]
34
+ action = action_s.to_sym()
35
+ raise("Invalid action '#{action_s}'") if GitMaintain::getActionAttr("ACTION_LIST").index(action) == nil
36
+ opts[:action] = action
37
+ ARGV.shift()
38
+
39
+ optsParser = OptionParser.new(nil, 60)
40
+ optsParser.banner = "Usage: #{__FILE__} #{action_s} "
41
+ optsParser.separator ""
42
+ optsParser.separator "Options:"
43
+ optsParser.on("-h", "--help", "Display usage.") { |val| puts optsParser.to_s; exit 0 }
44
+ optsParser.on("-b", "--branch-suffix [SUFFIX]", "Branch suffix. Default is 'master'.") {
45
+ |val| opts[:br_suff] = val}
46
+ optsParser.on("-n", "--no", "Assume no to all questions.") {
47
+ |val| opts[:no] = true}
48
+ GitMaintain::setOpts(action, optsParser, opts)
49
+
50
+ rest = optsParser.order!(ARGV);
51
+ raise("Extra Unexpected extra arguments provided: " + rest.map(){|x|"'" + x + "'"}.join(", ")) if rest.length != 0
52
+
53
+ GitMaintain::checkOpts(opts)
54
+ GitMaintain::execAction(opts, opts[:action])
55
+
@@ -0,0 +1,99 @@
1
+ export _GIT_MAINTAIN_CMD_AWK=$(([ -f /bin/awk ] && echo "/bin/awk") || echo "/usr/bin/awk")
2
+ export _GIT_MAINTAIN_CMD_SORT=$(([ -f /bin/sort ] && echo "/bin/sort") || echo "/usr/bin/sort")
3
+ export _GIT_MAINTAIN_CMD_EGREP=$(([ -f /bin/egrep ] && echo "/bin/egrep") || echo "/usr/bin/egrep")
4
+ export _GIT_MAINTAIN_CMD_SED=$(([ -f /bin/sed ] && echo "/bin/sed") || echo "/usr/bin/sed")
5
+
6
+ _git_maintain_genoptlist(){
7
+ local COMMAND=$*
8
+ ${COMMAND} --help 2>&1 | \
9
+ ${_GIT_MAINTAIN_CMD_AWK} 'BEGIN { found = 0 } { if(found == 1) print $$0; if($$1 == "Options:") {found = 1}}' | \
10
+ ${_GIT_MAINTAIN_CMD_EGREP} -e "^[[:space:]]*--" -e "^[[:space:]]*-[a-zA-Z0-9]" | \
11
+ ${_GIT_MAINTAIN_CMD_SED} -e 's/^[[:space:]]*//' -e 's/^-[^-], //' | \
12
+ ${_GIT_MAINTAIN_CMD_AWK} '{ print $1}' | \
13
+ ${_GIT_MAINTAIN_CMD_SED} -e 's/^\(.*\)\[no-\]\(.*$\)/\1\2\n\1no-\2/' | \
14
+ ${_GIT_MAINTAIN_CMD_SORT} -u
15
+ }
16
+ _complete_git_maintain_branch(){
17
+ case $prev in
18
+ -b|--branch-suffix)
19
+ __gitcomp_nl "$(git maintain list_suffixes)"
20
+ ;;
21
+ -v|--base-version)
22
+ BRANCHES=
23
+ __gitcomp_nl "$(git maintain list_branches)"
24
+ ;;
25
+ esac
26
+ }
27
+
28
+ _git_maintain_cp()
29
+ {
30
+ local OPT_LIST=$(_git_maintain_genoptlist git maintain cp)
31
+
32
+ case "$prev" in
33
+ -c|--sha1);;
34
+ *)
35
+ __gitcomp_nl "$OPT_LIST"
36
+ _complete_git_maintain_branch
37
+ ;;
38
+ esac;
39
+ }
40
+
41
+ _git_maintain_merge()
42
+ {
43
+ local OPT_LIST=$(_git_maintain_genoptlist git maintain merge)
44
+ _get_comp_words_by_ref cur
45
+
46
+ case "$prev" in
47
+ -m|--merge)
48
+ __gitcomp_nl "$(git maintain list_suffixes)"
49
+ ;;
50
+ *)
51
+ __gitcomp_nl "$OPT_LIST"
52
+ _complete_git_maintain_branch
53
+ ;;
54
+ esac;
55
+ }
56
+
57
+ _git_maintain(){
58
+ local direct_call=${1:-1}
59
+ local cmd_word=$(expr $direct_call + 1)
60
+
61
+ __git_has_doubledash && return
62
+
63
+ _get_comp_words_by_ref cur
64
+ _get_comp_words_by_ref prev
65
+ _get_comp_words_by_ref cword
66
+
67
+
68
+ if [ $cword -eq $cmd_word ]; then
69
+ case "$cur" in
70
+ -*)
71
+ __gitcomp_nl "$(_git_maintain_genoptlist git maintain)"
72
+ return
73
+ ;;
74
+ *)
75
+ __gitcomp_nl "$(git maintain list_actions | grep -v list_actions)"
76
+ return
77
+ ;;
78
+ esac
79
+ else
80
+ _get_comp_words_by_ref words
81
+ local cmd_name=${words[$cmd_word]}
82
+ completion_func="_git_maintain_${cmd_name}"
83
+ declare -f $completion_func > /dev/null
84
+ if [ $? -eq 0 ]; then
85
+ $completion_func
86
+ else
87
+ OPT_LIST=$(_git_maintain_genoptlist git maintain $cmd_name)
88
+ case "$prev" in
89
+ *)
90
+ __gitcomp_nl "$OPT_LIST"
91
+ esac
92
+ fi
93
+ fi
94
+
95
+ }
96
+
97
+ __git_maintain(){
98
+ _git_maintain 0
99
+ } && complete -F __git_maintain git-maintain
@@ -0,0 +1,61 @@
1
+ module GitMaintain
2
+ class RDMACoreBranch < Branch
3
+ REPO_NAME = "rdma-core"
4
+
5
+ def release(opts)
6
+ prev_ver=@repo.runGit("show HEAD:CMakeLists.txt | egrep \"[sS][eE][tT]\\\\(PACKAGE_VERSION\"").
7
+ chomp().gsub(/[sS][eE][tT]\(PACKAGE_VERSION\s*"([0-9.]*)".*$/, '\1')
8
+ ver_nums = prev_ver.split(".")
9
+ new_ver = (ver_nums[0 .. -2] + [ver_nums[-1].to_i() + 1 ]).join(".")
10
+ git_prev_ver = "v" + (ver_nums[-1] == "0" ? ver_nums[0 .. -2].join(".") : prev_ver)
11
+
12
+ puts "Preparing release #{prev_ver} => #{new_ver}"
13
+ rep = GitMaintain::checkLog(opts, @local_branch, git_prev_ver, "release")
14
+ if rep != "y" then
15
+ puts "Skipping release"
16
+ return
17
+ end
18
+
19
+ # Prepare tag message
20
+ tag_path=`mktemp`.chomp()
21
+ puts tag_path
22
+ tag_file = File.open(tag_path, "w+")
23
+ tag_file.puts "rdma-core-#{new_ver}:"
24
+ tag_file.puts ""
25
+ tag_file.puts "Updates from version #{prev_ver}"
26
+ tag_file.puts " * Backport fixes:"
27
+ tag_file.puts `git log HEAD ^#{git_prev_ver} --format=' * %s'`
28
+ tag_file.close()
29
+
30
+ # Update version number in relevant files
31
+ @repo.run("sed -i -e 's/\\(Version:[[:space:]]*\\)[0-9.]*/\\1#{new_ver}/g' redhat/rdma-core.spec suse/rdma-core.spec")
32
+ @repo.run("sed -i -e 's/\\([sS][eE][tT](PACKAGE_VERSION[[:space:]]*\"\\)[0-9.]*\"/\\1#{new_ver}\"/g' CMakeLists.txt")
33
+
34
+ @repo.run("cat <<EOF > debian/changelog.new
35
+ rdma-core (#{new_ver}-1) unstable; urgency=low
36
+
37
+ * New upstream release.
38
+
39
+ -- $(git config user.name) <$(git config user.email)> $(date '+%a, %d %b %Y %T %z')
40
+
41
+ $(cat debian/changelog)
42
+ EOF
43
+ mv debian/changelog.new debian/changelog")
44
+
45
+ # Add and commit
46
+ @repo.runGit("add redhat/rdma-core.spec suse/rdma-core.spec CMakeLists.txt debian/changelog")
47
+ @repo.runGit("commit -m 'Bump to version #{new_ver}' --verbose --edit --signoff")
48
+ if $? != 0 then
49
+ raise("Failed to commit on branch #{local_branch}")
50
+ end
51
+ @repo.runGit("tag -a -s v#{new_ver} --edit -F #{tag_path}")
52
+ if $? != 0 then
53
+ raise("Failed to tag branch #{local_branch}")
54
+ end
55
+ `rm -f #{tag_path}`
56
+ end
57
+ end
58
+
59
+ GitMaintain::registerCustom(RDMACoreBranch::REPO_NAME,
60
+ { GitMaintain::Branch => RDMACoreBranch })
61
+ end
data/lib/branch.rb ADDED
@@ -0,0 +1,460 @@
1
+ module GitMaintain
2
+
3
+ class CherryPickErrorException < StandardError
4
+ def initialize(str, commit)
5
+ @commit = commit
6
+ super(str)
7
+ end
8
+ attr_reader :commit
9
+ end
10
+
11
+ class Branch
12
+ ACTION_LIST = [
13
+ :cp, :steal, :list, :merge,
14
+ :push, :monitor,
15
+ :push_stable, :monitor_stable,
16
+ :release, :reset
17
+ ]
18
+ NO_FETCH_ACTIONS = [
19
+ :cp, :merge, :monitor, :release
20
+ ]
21
+ NO_CHECKOUT_ACTIONS = [
22
+ :list, :push, :monitor, :monitor_stable
23
+ ]
24
+ ACTION_HELP = [
25
+ "* cp: Backport commits and eventually push them to github",
26
+ "* steal: Steal commit from upstream that fixes commit in the branch or were tagged as stable",
27
+ "* list: List commit present in the branch but not in the stable branch",
28
+ "* merge: Merge branch with suffix specified in -m <suff> into the main branch",
29
+ "* push: Push branches to github for validation",
30
+ "* monitor: Check the travis state of all branches",
31
+ "* push_stable: Push to stable repo",
32
+ "* monitor_stable: Check the travis state of all stable branches",
33
+ "* release: Create new release on all concerned branches",
34
+ "* reset: Reset branch against upstream",
35
+ ]
36
+
37
+ def self.load(repo, version, travis, branch_suff)
38
+ repo_name = File.basename(repo.path)
39
+ return GitMaintain::loadClass(Branch, repo_name, repo, version, travis, branch_suff)
40
+ end
41
+
42
+ def self.set_opts(action, optsParser, opts)
43
+ opts[:base_ver] = 0
44
+ opts[:version] = /.*/
45
+ opts[:commits] = []
46
+ opts[:do_merge] = false
47
+ opts[:push_force] = false
48
+ opts[:no_travis] = false
49
+
50
+ optsParser.on("-v", "--base-version [MIN_VER]", Integer, "Older release to consider.") {
51
+ |val| opts[:base_ver] = val}
52
+ optsParser.on("-V", "--version [regexp]", Regexp, "Regexp to filter versions.") {
53
+ |val| opts[:version] = val}
54
+
55
+ case action
56
+ when :cp
57
+ optsParser.banner += "-c <sha1> [-c <sha1> ...]"
58
+ optsParser.on("-c", "--sha1 [SHA1]", String, "Commit to cherry-pick. Can be used multiple time.") {
59
+ |val| opts[:commits] << val}
60
+ when :merge
61
+ optsParser.banner += "-m <suffix>"
62
+ optsParser.on("-m", "--merge [SUFFIX]", "Merge branch with suffix.") {
63
+ |val| opts[:do_merge] = val}
64
+ when :push
65
+ optsParser.banner += "[-f]"
66
+ optsParser.on("-f", "--force", "Add --force to git push (for 'push' action).") {
67
+ |val| opts[:push_force] = val}
68
+ when :push_stable
69
+ optsParser.banner += "[-T]"
70
+ optsParser.on("-T", "--no-travis", "Ignore Travis build status and push anyway.") {
71
+ |val| opts[:no_travis] = val}
72
+ end
73
+ end
74
+
75
+ def self.check_opts(opts)
76
+ if opts[:action] == :push_stable ||
77
+ opts[:action] == :release then
78
+ if opts[:br_suff] != "master" then
79
+ raise "Action #{opts[:action]} can only be done on 'master' suffixed branches"
80
+ end
81
+ end
82
+ end
83
+
84
+ def self.execAction(opts, action)
85
+ repo = Repo::load()
86
+ travis = TravisChecker::load(repo)
87
+
88
+ if NO_FETCH_ACTIONS.index(action) == nil then
89
+ repo.stableUpdate()
90
+ end
91
+
92
+ repo.getStableList(opts[:br_suff]).each(){|br|
93
+ branch = Branch::load(repo, br, travis, opts[:br_suff])
94
+ case branch.is_targetted?(opts)
95
+ when :too_old
96
+ puts "# Skipping older v#{branch.version}"
97
+ next
98
+ when :no_match
99
+ puts "# Skipping v#{branch.version} not matching #{opts[:version].to_s()}"
100
+ next
101
+ end
102
+
103
+ puts "###############################"
104
+ puts "# Working on v#{branch.version}"
105
+ puts "###############################"
106
+
107
+ if NO_CHECKOUT_ACTIONS.index(action) == nil then
108
+ branch.checkout()
109
+ end
110
+ branch.send(action, opts)
111
+ }
112
+ end
113
+
114
+ def initialize(repo, version, travis, branch_suff)
115
+ GitMaintain::checkDirectConstructor(self.class)
116
+
117
+ @repo = repo
118
+ @version = version
119
+ @travis = travis
120
+ @branch_suff = branch_suff
121
+
122
+ @local_branch = "dev/stable-v#{@version}/#{@branch_suff}"
123
+ @head = @repo.runGit("rev-parse #{@local_branch}")
124
+
125
+ @remote_branch ="stable-v#{@version}"
126
+ @remote_ref = "#{Repo::STABLE_REPO}/#{@remote_branch}"
127
+ @stable_head = @repo.runGit("rev-parse #{@remote_ref}")
128
+ @stable_base = @repo.findStableBase(@local_branch)
129
+ end
130
+ attr_reader :version, :local_branch, :head, :remote_branch, :remote_ref, :stable_head
131
+
132
+ def is_targetted?(opts)
133
+ if @version.to_i < opts[:base_ver] then
134
+ return :too_old
135
+ end
136
+ if @version !~ opts[:version] then
137
+ return :no_match
138
+ end
139
+ return true
140
+ end
141
+
142
+ # Checkout the repo to the given branch
143
+ def checkout()
144
+ print @repo.runGit("checkout -q #{@local_branch}")
145
+ if $? != 0 then
146
+ raise "Error: Failed to checkout the branch"
147
+ end
148
+ end
149
+
150
+ # Cherry pick an array of commits
151
+ def cherry_pick(opts)
152
+ if opts[:commits].length > 0 then
153
+ @repo.runGit("cherry-pick #{opts[:commits].join(" ")}")
154
+ if $? != 0 then
155
+ puts "Cherry pick failure. Starting bash for manual fixes. Exit shell to continue"
156
+ @repo.runSystem("bash")
157
+ puts "Continuing..."
158
+ end
159
+ end
160
+ end
161
+
162
+ # Steal upstream commits that are not in the branch
163
+ def steal(opts)
164
+ steal_all(opts, "#{@stable_base}..origin/master")
165
+ end
166
+
167
+ # List commits in the branch that are no in the stable branch
168
+ def list(opts)
169
+ GitMaintain::checkLog(opts, @local_branch, @remote_ref, nil)
170
+ end
171
+
172
+ # Merge merge_branch into this one
173
+ def merge(opts)
174
+ merge_branch = "dev/stable-v#{@version}/#{opts[:do_merge]}"
175
+ rep = GitMaintain::checkLog(opts, merge_branch, @local_branch, "merge")
176
+ if rep == "y" then
177
+ @repo.runGit("merge #{merge_branch}")
178
+ if $? != 0 then
179
+ puts "Merge failure. Starting bash for manual fixes. Exit shell to continue"
180
+ @repo.runSystem("bash")
181
+ puts "Continuing..."
182
+ end
183
+ else
184
+ puts "Skipping merge"
185
+ return
186
+ end
187
+ end
188
+
189
+ # Push the branch to the validation repo
190
+ def push(opts)
191
+ @repo.runGit("push #{opts[:push_force] == true ? "-f" : ""} #{Repo::VALID_REPO} #{@local_branch}")
192
+ end
193
+
194
+ # Monitor the build status on Travis
195
+ def monitor(opts)
196
+ st = @travis.getValidState(head)
197
+ puts "Status for v#{@version}: " + st
198
+ if st == "failed"
199
+ rep = "y"
200
+ suff=""
201
+ while rep == "y"
202
+ rep = GitMaintain::confirm(opts, "see the build log#{suff}")
203
+ if rep == "y" then
204
+ log = @travis.getValidLog(head)
205
+ tmp = `mktemp`.chomp()
206
+ tmpfile = File.open(tmp, "w+")
207
+ tmpfile.puts(log)
208
+ tmpfile.close()
209
+ system("less -r #{tmp}")
210
+ `rm -f #{tmp}`
211
+ end
212
+ suff=" again"
213
+ end
214
+ end
215
+ end
216
+
217
+ # Push branch to the stable repo
218
+ def push_stable(opts)
219
+ if opts[:no_travis] != true &&
220
+ @travis.checkValidState(@head) != true then
221
+ puts "Build is not passed on travis. Skipping push to stable"
222
+ return
223
+ end
224
+ rep = GitMaintain::checkLog(opts, @local_branch, @remote_ref, "submit")
225
+ if rep == "y" then
226
+ @repo.runGit("push #{Repo::STABLE_REPO} #{@local_branch}:#{@remote_branch}")
227
+ else
228
+ puts "Skipping push to stable"
229
+ return
230
+ end
231
+ end
232
+
233
+ # Monitor the build status of the stable branch on Travis
234
+ def monitor_stable(opts)
235
+ puts "Status for v#{@version}: " + @travis.getStableState(@stable_head)
236
+ end
237
+
238
+ # Reset the branch to the upstream stable one
239
+ def reset(opts)
240
+ rep = GitMaintain::checkLog(opts, @local_branch, @remote_ref, "reset")
241
+ if rep == "y" then
242
+ @repo.runGit("reset --hard #{@remote_ref}")
243
+ else
244
+ puts "Skipping reset"
245
+ return
246
+ end
247
+ end
248
+
249
+ def release(opts)
250
+ puts "#No release command available for this repo"
251
+ end
252
+
253
+ private
254
+ def add_blacklist(commit)
255
+ @repo.runGit("notes append -m \"#{@local_branch}\" #{commit}")
256
+ end
257
+
258
+ def is_blacklisted?(commit)
259
+ @repo.runGit("notes show #{commit} 2> /dev/null").split("\n").each(){|br|
260
+ return true if br == @local_branch
261
+ }
262
+ return false
263
+ end
264
+
265
+ def make_pretty(orig_commit, commit="")
266
+ orig_sha=@repo.runGit("rev-parse #{orig_commit}")
267
+ msg_commit = (commit.to_s() == "") ? orig_sha : commit
268
+
269
+ msg_path=`mktemp`.chomp()
270
+ msg_file = File.open(msg_path, "w+")
271
+ msg_file.puts @repo.runGit("log -1 --format=\"%s%n%n[ Upstream commit #{msg_commit} ]%n%n%b\" #{orig_commit}")
272
+ msg_file.close()
273
+ @repo.runGit("commit -s --amend -F #{msg_path}")
274
+ `rm -f #{msg_path}`
275
+ end
276
+
277
+ def is_in_tree?(commit)
278
+ fullhash=@repo.runGit("rev-parse #{commit}")
279
+ # This might happen if someone pointed to a commit that doesn't exist in our
280
+ # tree.
281
+ if $? != 0 then
282
+ return false
283
+ end
284
+
285
+ # Hope for the best, same commit is/isn't in the current branch
286
+ if @repo.runGit("merge-base #{fullhash} HEAD") == fullhash then
287
+ return true
288
+ end
289
+
290
+ # Grab the subject, since commit sha1 is different between branches we
291
+ # have to look it up based on subject.
292
+ subj=@repo.runGit("log -1 --pretty=\"%s\" #{commit}")
293
+ if $? != 0 then
294
+ return false
295
+ end
296
+
297
+ # Try and find if there's a commit with given subject the hard way
298
+ @repo.runGit("log --pretty=\"%H\" -F --grep \"#{subj}\" "+
299
+ "#{@stable_base}..HEAD").split("\n").each(){|cmt|
300
+ cursubj=@repo.runGit("log -1 --format=\"%s\" #{cmt}")
301
+ if cursubj = subj then
302
+ return true
303
+ end
304
+ }
305
+ return false
306
+ end
307
+
308
+ def is_relevant?(commit)
309
+ # Let's grab the commit that this commit fixes (if exists (based on the "Fixes:" tag)).
310
+ fixescmt=@repo.runGit("log -1 #{commit} | grep -i \"fixes:\" | head -n 1 | "+
311
+ "sed -e 's/^[ \\t]*//' | cut -f 2 -d ':' | "+
312
+ "sed -e 's/^[ \\t]*//' -e 's/\\([0-9a-f]\\+\\)(/\\1 (/' | cut -f 1 -d ' '")
313
+
314
+ # If this commit fixes anything, but the broken commit isn't in our branch we don't
315
+ # need this commit either.
316
+ if fixescmt != "" then
317
+ if is_in_tree?(fixescmt) then
318
+ return true
319
+ else
320
+ return false
321
+ end
322
+ end
323
+
324
+ if @repo.runGit("show #{commit} | grep -i 'stable@' | wc -l") == "0" then
325
+ return false
326
+ end
327
+
328
+ # Let's see if there's a version tag in this commit
329
+ full=@repo.runGit("show #{commit} | grep -i 'stable@'").gsub(/.* /, "")
330
+
331
+ # Sanity check our extraction
332
+ if full =~ /stable/ then
333
+ return false
334
+ end
335
+
336
+ # Make sure our branch contains this version
337
+ if @repo.runGit("merge-base #{@head} #{full}") == full then
338
+ return true
339
+ end
340
+
341
+ # Tag is not in history, ignore
342
+ return false
343
+ end
344
+
345
+ def pick_one(commit)
346
+ @repo.runGit("cherry-pick --strategy=recursive -Xpatience -x #{commit} &> /dev/null")
347
+ return if $? == 0
348
+
349
+ if [ @repo.runGit("status -uno --porcelain | wc -l") != 0 ]; then
350
+ @repo.runGit("reset --hard")
351
+ return
352
+ end
353
+ @repo.runGit("reset --hard")
354
+ # That didn't work? Let's try that with every variation of the commit
355
+ # in other stable trees.
356
+ find_alts(commit).each(){|alt_commit|
357
+ @repo.runCmd("cherry-pick --strategy=recursive -Xpatience -x #{alt_commit} &> /dev/null")
358
+ if $? == 0 then
359
+ return
360
+ end
361
+ @repo.runCmd("reset --hard")
362
+ }
363
+ # Still no? Let's go back to the original commit and hand it off to
364
+ # the user.
365
+ @repo.runCmd("cherry-pick --strategy=recursive -Xpatience -x #{commit} &> /dev/null")
366
+ raise CherryPickErrorException.new("Failed to cherry pick commit #{commit}", commit)
367
+ return false
368
+ end
369
+
370
+ def confirm_one(opts, commit)
371
+ rep=""
372
+ do_cp=false
373
+ puts @repo.runGit("show --format=oneline --no-patch --no-decorate #{commit}")
374
+ while rep != "y" do
375
+ puts "Do you want to steal this commit ? (y/n/b/?)"
376
+ if opts[:no] == true then
377
+ puts "Auto-replying no due to --no option"
378
+ rep = 'n'
379
+ break
380
+ else
381
+ rep = STDIN.gets.chomp()
382
+ end
383
+ case rep
384
+ when "n"
385
+ puts "Skip this commit"
386
+ break
387
+ when "b"
388
+ puts "Blacklisting this commit for the current branch"
389
+ add_blacklist(commit)
390
+ break
391
+ when "y"
392
+ rep="y"
393
+ do_cp=true
394
+ break
395
+ when "?"
396
+ puts @repo.runGit("show #{commit}")
397
+ else
398
+ STDERR.puts "Invalid answer $rep"
399
+ puts @repo.runGit("show --format=oneline --no-patch --no-decorate #{commit}")
400
+ end
401
+ end
402
+ return do_cp
403
+ end
404
+
405
+ def steal_one(opts, commit)
406
+ subj=@repo.runGit("log -1 --format=\"%s\" #{commit}")
407
+ msg=''
408
+
409
+ # Let's grab the mainline commit id, this is useful if the version tag
410
+ # doesn't exist in the commit we're looking at but exists upstream.
411
+ orig_cmt=@repo.runGit("log --no-merges --format=\"%H\" -F --grep \"#{subj}\" " +
412
+ "#{@stable_base}..origin/master | tail -n1")
413
+
414
+ # If the commit doesn't apply for us, skip it
415
+ if is_relevant?(orig_cmt) != true
416
+ return
417
+ end
418
+
419
+ if is_in_tree?(orig_cmt) == true
420
+ # Commit is already in the stable branch, skip
421
+ return
422
+ end
423
+
424
+ # Check if it's not blacklisted by a git-notes
425
+ if is_blacklisted?(orig_cmt) == true then
426
+ # Commit is blacklisted
427
+ puts "Skipping 'blacklisted' commit " +
428
+ @repo.runGit("show --format=oneline --no-patch --no-decorate #{orig_cmt}")
429
+ return
430
+ end
431
+
432
+ do_cp = confirm_one(opts, orig_cmt)
433
+ return if do_cp != true
434
+
435
+ begin
436
+ pick_one(commit)
437
+ rescue CherryPickErrorException => e
438
+ puts "Cherry pick failed. Fix, commit (or reset) and exit."
439
+ @repo.runSystem("/bin/bash")
440
+ return
441
+ end
442
+
443
+ # If we didn't find the commit upstream then this must be a custom commit
444
+ # in the given tree - make sure the user checks this commit.
445
+ if orig_cmt == "" then
446
+ msg="Custom"
447
+ orig_cmt=@repo.runGit("rev-parse HEAD")
448
+ puts "Custom commit, please double-check!"
449
+ @repo.runSystem("/bin/bash")
450
+ end
451
+ make_pretty(orig_cmt, msg)
452
+ end
453
+
454
+ def steal_all(opts, range)
455
+ @repo.runGit("log --no-merges --format=\"%H\" #{range} | tac").split("\n").each(){|commit|
456
+ steal_one(opts, commit)
457
+ }
458
+ end
459
+ end
460
+ end
data/lib/common.rb ADDED
@@ -0,0 +1,123 @@
1
+ $LOAD_PATH.push(BACKPORT_LIB_DIR)
2
+
3
+ require 'travis'
4
+ require 'repo'
5
+ require 'branch'
6
+
7
+ $LOAD_PATH.pop()
8
+
9
+ module GitMaintain
10
+ class Common
11
+ ACTION_LIST = [ :list_actions ]
12
+ ACTION_HELP = []
13
+ def self.execAction(opts, action)
14
+ puts GitMaintain::getActionAttr("ACTION_LIST").join("\n")
15
+ end
16
+ end
17
+
18
+ ACTION_CLASS = [ Common, Branch, Repo ]
19
+ @@custom_classes = {}
20
+
21
+ def registerCustom(repo_name, classes)
22
+ raise("Multiple class for repo #{repo_name}") if @@custom_classes[repo_name] != nil
23
+ @@custom_classes[repo_name] = classes
24
+ end
25
+ module_function :registerCustom
26
+
27
+ def getCustom(repo_name)
28
+ return @@custom_classes[repo_name]
29
+ end
30
+ module_function :getCustom
31
+
32
+ def loadClass(default_class, repo_name, *more)
33
+ custom = @@custom_classes[repo_name]
34
+ if custom != nil && custom[default_class] != nil then
35
+ puts "# Detected custom #{default_class} class for repo '#{repo_name}'" if ENV['DEBUG'] == "1"
36
+ return custom[default_class].new(*more)
37
+ else
38
+ puts "# Detected NO custom #{default_class} classes for repo '#{repo_name}'" if ENV['DEBUG'] == "1"
39
+ return default_class.new(*more)
40
+ end
41
+ end
42
+ module_function :loadClass
43
+
44
+ # Check that the constructor was called through loadClass
45
+ def checkDirectConstructor(theClass)
46
+ # Look for the "new" in the calling tree
47
+ depth = 1
48
+ while caller_locations(depth, 1)[0].label != "new"
49
+ depth +=1
50
+ end
51
+ # The function that called the constructer is just one step below
52
+ raise("Use GitMaintain::loadClass to construct a #{theClass} class") if
53
+ caller_locations(depth + 1, 1)[0].label != "loadClass"
54
+ end
55
+ module_function :checkDirectConstructor
56
+
57
+ def getActionAttr(attr)
58
+ return ACTION_CLASS.map(){|x| x.const_get(attr)}.flatten()
59
+ end
60
+ module_function :getActionAttr
61
+
62
+ def setOpts(action, optsParser, opts)
63
+ ACTION_CLASS.each(){|x|
64
+ next if x::ACTION_LIST.index(action) == nil
65
+ next if x.singleton_methods().index(:set_opts) == nil
66
+ x.set_opts(action, optsParser, opts)
67
+ break
68
+ }
69
+ end
70
+ module_function :setOpts
71
+
72
+ def checkOpts(opts)
73
+ ACTION_CLASS.each(){|x|
74
+ next if x.singleton_methods().index(:check_opts) == nil
75
+ x.check_opts(opts)
76
+ }
77
+ end
78
+ module_function :checkOpts
79
+
80
+ def execAction(opts, action)
81
+ ACTION_CLASS.each(){|x|
82
+ next if x::ACTION_LIST.index(action) == nil
83
+ x.execAction(opts, action)
84
+ break
85
+ }
86
+ end
87
+ module_function :execAction
88
+
89
+ def confirm(opts, msg)
90
+ rep = 't'
91
+ while rep != "y" && rep != "n" && rep != '' do
92
+ puts "Do you wish to #{msg} ? (y/N): "
93
+ if opts[:no] == true then
94
+ puts "Auto-replying no due to --no option"
95
+ rep = 'n'
96
+ else
97
+ rep = STDIN.gets.chomp()
98
+ end
99
+ end
100
+ return rep
101
+ end
102
+ module_function :confirm
103
+
104
+ def checkLog(opts, br1, br2, action_msg)
105
+ puts "Diff between #{br1} and #{br2}"
106
+ puts `git shortlog #{br1} ^#{br2}`
107
+ return "n" if action_msg.to_s() == ""
108
+ rep = confirm(opts, "#{action_msg} this branch")
109
+ return rep
110
+ end
111
+ module_function :checkLog
112
+
113
+ end
114
+ $LOAD_PATH.pop()
115
+
116
+
117
+ # Load all custom classes
118
+ $LOAD_PATH.push(BACKPORT_LIB_DIR + "/addons/")
119
+ Dir.entries(BACKPORT_LIB_DIR + "/addons/").each(){|entry|
120
+ next if (!File.file?(BACKPORT_LIB_DIR + "/addons/" + entry) || entry !~ /\.rb$/ );
121
+ require entry.sub(/.rb$/, "")
122
+ }
123
+ $LOAD_PATH.pop()
data/lib/repo.rb ADDED
@@ -0,0 +1,182 @@
1
+ module GitMaintain
2
+ class Repo
3
+ VALID_REPO = "github"
4
+ STABLE_REPO = "stable"
5
+ SUBMIT_BINARY="/usr/bin/git-release.ruby2.5"
6
+
7
+ ACTION_LIST = [
8
+ :list_branches,
9
+ # Internal commands for completion
10
+ :list_suffixes, :submit_release
11
+ ]
12
+ ACTION_HELP = [
13
+ "* submit_release: Push the to stable and create the release packages",
14
+ ]
15
+
16
+ def self.load(path=".")
17
+ dir = Dir.pwd()
18
+ repo_name = File.basename(dir)
19
+ return GitMaintain::loadClass(Repo, repo_name, dir)
20
+ end
21
+
22
+ def self.check_opts(opts)
23
+ if opts[:action] == :submit_release then
24
+ if opts[:br_suff] != "master" then
25
+ raise "Action #{opts[:action]} can only be done on 'master' suffixed branches"
26
+ end
27
+ end
28
+ end
29
+
30
+ def self.execAction(opts, action)
31
+ repo = Repo::load()
32
+
33
+ if action == :submit_release then
34
+ repo.stableUpdate()
35
+ end
36
+ repo.send(action, opts)
37
+ end
38
+
39
+ def initialize(path=nil)
40
+ GitMaintain::checkDirectConstructor(self.class)
41
+
42
+ @path = path
43
+ @stable_list=nil
44
+ @stable_branches=nil
45
+ @suffix_list=nil
46
+
47
+ if path == nil
48
+ @path = Dir.pwd()
49
+ end
50
+ @remote_valid=runGit("remote -v | egrep '^#{VALID_REPO}' | grep fetch |
51
+ awk '{ print $2}' | sed -e 's/.*://' -e 's/\.git//'")
52
+ @remote_stable=runGit("remote -v | egrep '^#{STABLE_REPO}' | grep fetch |
53
+ awk '{ print $2}' | sed -e 's/.*://' -e 's/\.git//'")
54
+ @stable_base_patterns=
55
+ runGit("config --get-regexp stable-base | egrep '^stable-base\.' | "+
56
+ "sed -e 's/stable-base\.//' -e 's/---/\\//g'").split("\n").inject({}){ |m, x|
57
+ y=x.split(" ");
58
+ m[y[0]] = y[1]
59
+ m
60
+ }
61
+ end
62
+ attr_reader :path, :remote_valid, :remote_stable
63
+
64
+ def run(cmd)
65
+ return `cd #{@path} && #{cmd}`
66
+ end
67
+ def runSystem(cmd)
68
+ return system("cd #{@path} && #{cmd}")
69
+ end
70
+ def runGit(cmd)
71
+ if ENV["DEBUG"].to_s() != "" then
72
+ puts "Called from #{caller[1]}"
73
+ puts "Running git command '#{cmd}'"
74
+ end
75
+ return `git --work-tree=#{@path} #{cmd}`.chomp()
76
+ end
77
+ def runGitImap(cmd)
78
+ return `export GIT_ASKPASS=$(dirname $(dirname $(which git)))/lib/git-core/git-gui--askpass;
79
+ if [ ! -f $GIT_ASKPASS ]; then
80
+ export GIT_ASKPASS=$(dirname $(which git))/git-gui--askpass;
81
+ fi;
82
+ if [ ! -f $GIT_ASKPASS ]; then
83
+ export GIT_ASKPASS=/usr/lib/ssh/ssh-askpass;
84
+ fi; git --work-tree=#{@path} imap-send #{cmd}`
85
+ end
86
+
87
+ def stableUpdate()
88
+ puts "# Fetching stable updates..."
89
+ runGit("fetch #{STABLE_REPO}")
90
+ end
91
+ def getStableList(br_suff)
92
+ return @stable_list if @stable_list != nil
93
+
94
+ @stable_list=runGit("branch").split("\n").map(){|x|
95
+ x=~ /dev\/stable-v[0-9]+\/#{br_suff}/ ?
96
+ x.gsub(/\*?\s*dev\/stable-v([0-9]+)\/#{br_suff}\s*$/, '\1') :
97
+ nil}.compact().uniq()
98
+
99
+ return @stable_list
100
+ end
101
+
102
+ def getSuffixList()
103
+ return @suffix_list if @suffix_list != nil
104
+
105
+ @suffix_list = runGit("branch").split("\n").map(){|x|
106
+ x=~ /dev\/stable-v[0-9]+\/[a-zA-Z0-9_-]+/ ?
107
+ x.gsub(/\*?\s*dev\/stable-v[0-9]+\/([a-zA-Z0-9_-]+)\s*$/, '\1') :
108
+ nil}.compact().uniq()
109
+
110
+ return @suffix_list
111
+ end
112
+
113
+ def submitReleases()
114
+ remote_tags=runGit("ls-remote --tags #{STABLE_REPO} |
115
+ egrep 'refs/tags/v[0-9.]*$'").split("\n").map(){
116
+ |x| x.gsub(/.*refs\/tags\//, '')
117
+ }
118
+ local_tags =runGit("tag -l | egrep '^v[0-9.]*$'").split("\n")
119
+
120
+ new_tags = local_tags - remote_tags
121
+ if new_tags.empty? then
122
+ puts "All tags are already submitted."
123
+ # return
124
+ end
125
+
126
+ puts "This will officially release these tags: #{new_tags.join(", ")}"
127
+ rep = GitMaintain::confirm("release them")
128
+ if rep != 'y' then
129
+ raise "Aborting.."
130
+ end
131
+
132
+ mail_path=`mktemp`.chomp()
133
+ mail = File.open(mail_path, "w+")
134
+ mail.puts "From " + runGit("rev-parse HEAD") + " " + `date`.chomp()
135
+ mail.puts "From: " + runGit("config user.name") +
136
+ " <" + runGit("config user.email") +">"
137
+ mail.puts "To: " + runGit("config patch.target")
138
+ mail.puts "Date: " + `date -R`.chomp()
139
+ mail.puts "Subject: [ANNOUNCE] " + File.basename(@path) + " " +
140
+ (new_tags.length > 1 ?
141
+ (new_tags[0 .. -2].join(", ") + " and " + new_tags[-1]) :
142
+ new_tags.join(" ")) +
143
+ " has been tagged/released"
144
+ mail.puts ""
145
+ mail.puts "Here's the information from the tags:"
146
+ new_tags.sort().each(){|tag|
147
+ mail.puts `git show #{tag} --no-decorate -q | awk '!p;/^-----END PGP SIGNATURE-----/{p=1}'`
148
+ mail.puts ""
149
+ }
150
+ mail.puts "It's available at the normal places:"
151
+ mail.puts ""
152
+ mail.puts "git://github.com/#{@remote_stable}"
153
+ mail.puts "https://github.com/#{@remote_stable}/releases"
154
+ mail.close()
155
+
156
+ puts runGitImap("< #{mail_path}; rm -f #{mail_path}")
157
+
158
+ puts "Last chance to cancel before submitting"
159
+ rep= GitMaintain::confirm("submit these releases")
160
+ if rep != 'y' then
161
+ raise "Aborting.."
162
+ end
163
+ puts `#{SUBMIT_BINARY}`
164
+ end
165
+ def findStableBase(branch)
166
+ @stable_base_patterns.each(){|pattern, base|
167
+ return base if branch =~ /#{pattern}\//
168
+ }
169
+ raise("Could not a find a stable base for branch #{branch}")
170
+ end
171
+
172
+ def list_branches(opts)
173
+ puts getStableList(opts[:br_suff])
174
+ end
175
+ def list_suffixes(opts)
176
+ puts getSuffixList()
177
+ end
178
+ def submit_release(opts)
179
+ submitReleases()
180
+ end
181
+ end
182
+ end
data/lib/travis.rb ADDED
@@ -0,0 +1,105 @@
1
+ module GitMaintain
2
+ class TravisChecker
3
+ TRAVIS_URL='https://api.travis-ci.org/'
4
+
5
+ def self.load(repo)
6
+ repo_name = File.basename(repo.path)
7
+ return GitMaintain::loadClass(TravisChecker, repo_name, repo)
8
+ end
9
+
10
+ def initialize(repo)
11
+ GitMaintain::checkDirectConstructor(self.class)
12
+
13
+ @repo = repo
14
+ @cachedJson={}
15
+ end
16
+
17
+ private
18
+ def fetch(uri_str, limit = 10)
19
+ # You should choose a better exception.
20
+ raise ArgumentError, 'too many HTTP redirects' if limit == 0
21
+
22
+ response = Net::HTTP.get_response(URI(uri_str))
23
+
24
+ case response
25
+ when Net::HTTPSuccess then
26
+ response
27
+ when Net::HTTPRedirection then
28
+ location = response['location']
29
+ fetch(location, limit - 1)
30
+ else
31
+ response.value
32
+ end
33
+ end
34
+ def getJson(query_label, query, json=true)
35
+ return @cachedJson[query_label] if @cachedJson[query_label] != nil
36
+ url = TRAVIS_URL + query
37
+ uri = URI(url)
38
+ puts "# Querying travis..."
39
+ puts "# #{url}" if ENV["DEBUG_TRAVIS"].to_s() != ""
40
+ response = fetch(uri)
41
+ raise("Travis request failed '#{url}'") if response.code.to_s() != '200'
42
+
43
+ if json == true
44
+ @cachedJson[query_label] = JSON.parse(response.body)
45
+ else
46
+ @cachedJson[query_label] = response.body
47
+ end
48
+ return @cachedJson[query_label]
49
+ end
50
+ def getState(sha1, resp)
51
+ br = findBranch(sha1, resp)
52
+ return "not found" if br == nil
53
+
54
+ return br["state"]
55
+ end
56
+ def getLog(sha1, resp)
57
+ br = findBranch(sha1, resp)
58
+ raise("Travis build not found") if br == nil
59
+ job_id = br["job_ids"].last().to_s()
60
+ return getJson("log_" + job_id, 'jobs/' + job_id + '/log', false)
61
+ end
62
+ def checkState(sha1, resp)
63
+ return getState(sha1, resp) == "passed"
64
+ end
65
+
66
+ def getBrValidJson()
67
+ return getJson(:br_valid, 'repos/' + @repo.remote_valid + '/branches')
68
+ end
69
+ def getBrStableJson()
70
+ return getJson(:br_stable, 'repos/' + @repo.remote_stable + '/branches')
71
+ end
72
+ def findBranch(sha1, resp)
73
+ puts "# Looking for build for #{sha1}" if ENV["DEBUG_TRAVIS"].to_s() != ""
74
+ resp["branches"].each(){|br|
75
+ commit=resp["commits"].select(){|e| e["id"] == br["commit_id"]}.first()
76
+ raise("Incomplete JSON received from Travis") if commit == nil
77
+ puts "# Found entry for sha #{commit["sha"]}" if ENV["DEBUG_TRAVIS"].to_s() != ""
78
+ next if commit["sha"] != sha1
79
+ return br
80
+ }
81
+ return nil
82
+ end
83
+
84
+ public
85
+ def getValidState(sha1)
86
+ return getState(sha1, getBrValidJson())
87
+ end
88
+ def checkValidState(sha1)
89
+ return checkState(sha1, getBrValidJson())
90
+ end
91
+ def getValidLog(sha1)
92
+ return getLog(sha1, getBrValidJson())
93
+ end
94
+
95
+ def getStableState(sha1)
96
+ return getState(sha1, getBrStableJson())
97
+ end
98
+ def checkStableState(sha1)
99
+ return checkState(sha1, getBrStableJson())
100
+ end
101
+ def getStableLog(sha1)
102
+ return getLog(sha1, getBrStableJson())
103
+ end
104
+ end
105
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git-maintain
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Nicolas Morey-Chaisemartin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-07-13 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |-
14
+ Be lazy and let git-maintain do all the heavy lifting for maintaining stable branches.
15
+ Leaves you only with the essential: reviewing the selected patches and decide where they should go.
16
+ email: nmoreychaisemartin@suse.de
17
+ executables:
18
+ - git-maintain
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - LICENSE
23
+ - README.md
24
+ - bin/git-maintain
25
+ - git-maintain-completion.sh
26
+ - lib/addons/RDMACore.rb
27
+ - lib/branch.rb
28
+ - lib/common.rb
29
+ - lib/repo.rb
30
+ - lib/travis.rb
31
+ homepage: https://github.com/nmorey/git-maintain
32
+ licenses:
33
+ - MIT
34
+ metadata: {}
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubyforge_project:
51
+ rubygems_version: 2.7.3
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: Your ultimate script for maintaining stable branches.
55
+ test_files: []