git-maintain 0.12.0 → 0.14.2

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.
@@ -0,0 +1,148 @@
1
+ # Main module for git-maintain repository maintenance tool.
2
+ module GitMaintain
3
+
4
+ # Iterator class. List all the branch specific actions and trigger that action on all relevant branches
5
+ class BranchIterator < Common
6
+
7
+ begin
8
+ const_set(:EXTENDED_CLASS, GitMaintain::getExtendedClass(Branch))
9
+ rescue RunError
10
+ const_set(:EXTENDED_CLASS, Branch)
11
+ end
12
+
13
+ # List of all available maintenance actions.
14
+ [:ACTION_LIST, :NO_FETCH_ACTIONS, :NO_CHECKOUT_ACTIONS,
15
+ :ALL_BRANCHES_ACTIONS, :ACTION_HELP].each() do |field|
16
+ const_set(field, EXTENDED_CLASS.const_get(field))
17
+ end
18
+
19
+
20
+ # Configure action-specific command line options.
21
+ #
22
+ # @param action [Symbol] Selected action name
23
+ # @param optsParser [OptionParser] The OptionParser instance to configure
24
+ # @param opts [Hash] The options hash to populate
25
+ def self.set_opts(action, optsParser, opts)
26
+ Branch.set_opts(action, optsParser, opts)
27
+ if EXTENDED_CLASS != Branch && EXTENDED_CLASS.respond_to?(:set_opts)
28
+ EXTENDED_CLASS.set_opts(action, optsParser, opts)
29
+ end
30
+ end
31
+
32
+ # Sanity check and normalize the parsed options for the given action.
33
+ #
34
+ # @param opts [Hash] Options hash to validate and configure
35
+ # @raise [InvalidArgumentError] If options are invalid or conflicting
36
+ def self.check_opts(opts)
37
+ Branch.check_opts(opts)
38
+ if EXTENDED_CLASS != Branch && EXTENDED_CLASS.respond_to?(:check_opts)
39
+ EXTENDED_CLASS.check_opts(opts)
40
+ end
41
+ end
42
+
43
+ # Initialize the BranchIterator instance
44
+ def initialize()
45
+ @repo = Repo::load()
46
+ @ci = CI::load(@repo)
47
+ end
48
+ # Define wrappers for all actions, each calling iterateAction
49
+ ACTION_LIST.each do |action|
50
+ define_method(action) do |opts|
51
+ iterateAction(opts, action)
52
+ end
53
+ end
54
+
55
+ private
56
+ # Execute the specified action on the selected branch(es).
57
+ # Loads the repo, targets branches, iterates through them, and runs any action epilogue.
58
+ #
59
+ # @param opts [Hash] Options hash
60
+ # @param action [Symbol] Action name to execute
61
+ # @raise [GitMaintainError] If executing the action fails
62
+ def iterateAction(opts, action)
63
+ opts[:repo] = @repo
64
+ opts[:ci] = @ci
65
+
66
+ if NO_FETCH_ACTIONS.index(action) == nil && opts[:fetch] != false then
67
+ log(:INFO, "Fetching stable repo")
68
+ @repo.stableUpdate(opts[:fetch])
69
+ end
70
+
71
+ branches = getBranchList(opts, action)
72
+
73
+ if opts[:watch] == false
74
+ # One shot run
75
+ runOnBranches(opts, action, branches)
76
+ log(:INFO, "Done working on selected branches")
77
+ return
78
+ end
79
+
80
+ # Watch style action
81
+ loop do
82
+ # Timestamp on top, 'watch' style
83
+ system("clear; date")
84
+
85
+ runOnBranches(opts, action, branches)
86
+
87
+ sleep(opts[:watch])
88
+ @ci.emptyCache()
89
+ end
90
+ # No need for a log message here, the only exit condition
91
+ # is a Ctr-C that trigger an exception
92
+ end
93
+
94
+
95
+ # List the branches the iterator should loop on
96
+ # This can either be manually specified, or filtered by user or some specific command
97
+ #
98
+ # @param opts [Hash] Options hash
99
+ # @param action [Symbol] Action name to execute
100
+ # @return [Array<Branch>] Array of relevant branches
101
+ def getBranchList(opts, action)
102
+ # Direct branch selection
103
+ if opts[:manual_branch] != nil then
104
+ return [ Branch::load(@repo, opts[:manual_branch], @ci, opts[:br_suff]) ]
105
+ end
106
+
107
+ unfilteredList = nil
108
+ if ALL_BRANCHES_ACTIONS.index(action) != nil then
109
+ unfilteredList = @repo.getStableBranchList()
110
+ else
111
+ unfilteredList = @repo.getBranchList(opts[:br_suff])
112
+ end
113
+
114
+ return unfilteredList.map(){|br|
115
+ branch = Branch::load(@repo, br, @ci, opts[:br_suff])
116
+ case branch.is_targetted?(opts)
117
+ when :too_old
118
+ log(:VERBOSE, "Skipping older v#{branch.version}")
119
+ next
120
+ when :no_match
121
+ log(:VERBOSE, "Skipping v#{branch.version} not matching" +
122
+ opts[:version].to_s())
123
+ next
124
+ end
125
+ branch
126
+ }.compact()
127
+ end
128
+
129
+ def runOnBranches(opts, action, branches)
130
+ res=[]
131
+
132
+ # Iterate concerned on all branches
133
+ branches.each(){|branch|
134
+ if NO_CHECKOUT_ACTIONS.index(action) == nil then
135
+ log(:INFO, "Working on #{branch.verbose_name}")
136
+ branch.checkout()
137
+ end
138
+ res << branch.send(action, opts)
139
+ }
140
+
141
+ # Run epilogue (if it exists)
142
+ # Use the first branch to run it so we have an existing Object
143
+ if branches[0].respond_to?((action.to_s() + "_epilogue").to_sym())
144
+ branches[0].public_send(action.to_s() + "_epilogue", opts, res)
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,163 @@
1
+ # Main module for git-maintain repository maintenance tool.
2
+ module GitMaintain
3
+ # Abstract base class for CI provider adapters (e.g. TravisCI, AzureCI).
4
+ class CI < Common
5
+
6
+ # Factory method to load an instance of the CI class or its repository-specific subclass.
7
+ #
8
+ # @param repo [Repo] The Repo instance
9
+ # @return [CI] The loaded CI instance (or subclass)
10
+ # @raise [GitMaintainError] If class loading fails
11
+ def self.load(repo)
12
+ repo_name = File.basename(repo.path)
13
+ return GitMaintain::loadClass(CI, repo_name, repo)
14
+ end
15
+
16
+ # Initialize the CI instance.
17
+ #
18
+ # @param repo [Repo] The Repo instance
19
+ def initialize(repo)
20
+ GitMaintain::checkDirectConstructor(self.class)
21
+
22
+ @repo = repo
23
+ @cachedJson={}
24
+ end
25
+
26
+ private
27
+
28
+ # Fetch HTTP response from the specified URI string, following redirects up to the limit.
29
+ #
30
+ # @param uri_str [String, URI] The URI to fetch
31
+ # @param limit [Integer] Redirect follow limit
32
+ # @return [Net::HTTPResponse] The HTTP response object
33
+ # @raise [ArgumentError] If HTTP redirects exceed limit
34
+ def fetch(uri_str, limit = 10)
35
+ # You should choose a better exception.
36
+ raise ArgumentError, 'too many HTTP redirects' if limit == 0
37
+
38
+ response = Net::HTTP.get_response(URI(uri_str))
39
+
40
+ case response
41
+ when Net::HTTPSuccess then
42
+ response
43
+ when Net::HTTPRedirection then
44
+ location = response['location']
45
+ fetch(location, limit - 1)
46
+ else
47
+ response.value
48
+ end
49
+ end
50
+
51
+ # Retrieve JSON data (or raw body) from a CI query URL, with local caching.
52
+ #
53
+ # @param base_url [String] Base API/URL
54
+ # @param query_label [Symbol] Query label used for caching
55
+ # @param query [String] Query path
56
+ # @param json [Boolean] True to parse response body as JSON
57
+ # @return [Hash, String] The fetched JSON payload or raw body string
58
+ # @raise [GitMaintainError] If the HTTP request fails
59
+ def getJson(base_url, query_label, query, json=true)
60
+ return @cachedJson[query_label] if @cachedJson[query_label] != nil
61
+ url = base_url + query
62
+ uri = URI(url)
63
+ log(:INFO, "Querying CI...")
64
+ log(:DEBUG_CI, url)
65
+ response = fetch(uri)
66
+ raise GitMaintainError.new("CI request failed '#{url}'") if response.code.to_s() != '200'
67
+
68
+ if json == true
69
+ @cachedJson[query_label] = JSON.parse(response.body)
70
+ else
71
+ @cachedJson[query_label] = response.body
72
+ end
73
+ return @cachedJson[query_label]
74
+ end
75
+
76
+ public
77
+ # Retrieve the validation build state for a specific branch and commit.
78
+ #
79
+ # @param br [Branch] The Branch instance
80
+ # @param sha1 [String] Commit SHA
81
+ # @return [String] Build status string (e.g. 'passed')
82
+ def getValidState(br, sha1)
83
+ raise("Unimplemented")
84
+ end
85
+
86
+ # Check if the validation build is successful.
87
+ #
88
+ # @param br [Branch] The Branch instance
89
+ # @param sha1 [String] Commit SHA
90
+ # @return [Boolean] True if build passed
91
+ def checkValidState(br, sha1)
92
+ raise("Unimplemented")
93
+ end
94
+
95
+ # Retrieve the validation build log.
96
+ #
97
+ # @param br [Branch] The Branch instance
98
+ # @param sha1 [String] Commit SHA
99
+ # @return [String] Build log output text
100
+ def getValidLog(br, sha1)
101
+ raise("Unimplemented")
102
+ end
103
+
104
+ # Retrieve the validation build timestamp.
105
+ #
106
+ # @param br [Branch] The Branch instance
107
+ # @param sha1 [String] Commit SHA
108
+ # @return [String] Build timestamp
109
+ def getValidTS(br, sha1)
110
+ raise("Unimplemented")
111
+ end
112
+
113
+ # Retrieve the stable build state for a specific branch and commit.
114
+ #
115
+ # @param br [Branch] The Branch instance
116
+ # @param sha1 [String] Commit SHA
117
+ # @return [String] Build status string
118
+ def getStableState(br, sha1)
119
+ raise("Unimplemented")
120
+ end
121
+
122
+ # Check if the stable build is successful.
123
+ #
124
+ # @param br [Branch] The Branch instance
125
+ # @param sha1 [String] Commit SHA
126
+ # @return [Boolean] True if build passed
127
+ def checkStableState(br, sha1)
128
+ raise("Unimplemented")
129
+ end
130
+
131
+ # Retrieve the stable build log.
132
+ #
133
+ # @param br [Branch] The Branch instance
134
+ # @param sha1 [String] Commit SHA
135
+ # @return [String] Build log output text
136
+ def getStableLog(br, sha1)
137
+ raise("Unimplemented")
138
+ end
139
+
140
+ # Retrieve the stable build timestamp.
141
+ #
142
+ # @param br [Branch] The Branch instance
143
+ # @param sha1 [String] Commit SHA
144
+ # @return [String] Build timestamp
145
+ def getStableTS(br, sha1)
146
+ raise("Unimplemented")
147
+ end
148
+
149
+ # Clear local JSON request cache.
150
+ def emptyCache()
151
+ @cachedJson={}
152
+ end
153
+
154
+ # Check if the CI build status represents an error/failure.
155
+ #
156
+ # @param br [Branch] The Branch instance
157
+ # @param status [String] CI status string
158
+ # @return [Boolean] True if build errored or failed
159
+ def isErrored(br, status)
160
+ raise("Unimplemented")
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,117 @@
1
+ require 'cli_class_tool'
2
+
3
+ # Main module for git-maintain repository maintenance tool.
4
+ module GitMaintain
5
+ extend CLIClassTool::Utils
6
+ # Provide module-level log() for use from class methods (e.g. self.check_opts)
7
+ extend CLIClassTool::Logger
8
+
9
+ # Base class for git-maintain components providing generic logging and CLI utility helpers.
10
+ class Common < CLIClassTool::Common
11
+ public :log
12
+
13
+ # Log an error message and raise a GitMaintainError.
14
+ #
15
+ # @param msg [String] Error message
16
+ # @raise [GitMaintainError] Always raised with the given message
17
+ def crit(msg)
18
+ log(:ERROR, msg)
19
+ raise GitMaintainError.new(msg)
20
+ end
21
+
22
+ end
23
+
24
+ # Internal registry for custom repo-specific adapters.
25
+ @@custom_classes = {}
26
+
27
+ # Internal cached repo path and name
28
+ @@repo_infos = nil
29
+
30
+ # Register custom classes (Repo, Branch, CI) for a specific repository name.
31
+ #
32
+ # @param repo_name [String] Name of the repository (e.g. 'rdma-core')
33
+ # @param classes [Hash] Hash mapping default classes (e.g. Repo) to custom subclasses (e.g. RDMACoreRepo)
34
+ # @raise [GitMaintainError] If custom classes are already registered for this repository
35
+ def registerCustom(repo_name, classes)
36
+ raise GitMaintainError.new("Multiple class for repo #{repo_name}") if @@custom_classes[repo_name] != nil
37
+ classes[:name] = repo_name if classes[:name] == nil
38
+ @@custom_classes[repo_name] = classes
39
+ end
40
+ module_function :registerCustom
41
+
42
+ def getRepoInfos()
43
+ return @@repo_infos if @@repo_infos != nil
44
+
45
+ dir = File.realdirpath(".")
46
+ begin
47
+ repo_path = Common::run(dir, "git rev-parse --show-toplevel 2> /dev/null")
48
+ rescue RunError
49
+ raise NotARepoError.new(dir)
50
+ end
51
+ return @@repo_infos = [repo_path, File.basename(repo_path)]
52
+ end
53
+ module_function :getRepoInfos
54
+
55
+ # Retrieve the registered custom subclass for a given default class and repository name.
56
+ # Returns the default_class if no custom class is registered.
57
+ #
58
+ # @param default_class [Class] Default class to resolve (e.g. Repo)
59
+ # @param repo_name [String] Repository name to search for
60
+ # @return [Class] The resolved class (either the custom subclass or the default_class)
61
+ def getExtendedClass(default_class, repo_name=nil)
62
+ begin
63
+ (_repo_path, repo_name) = getRepoInfos() if repo_name == nil
64
+ custom = @@custom_classes[repo_name]
65
+ if custom != nil && custom[default_class] != nil then
66
+ return custom[default_class]
67
+ else
68
+ return default_class
69
+ end
70
+ rescue NotARepoError
71
+ return default_class
72
+ end
73
+ end
74
+ module_function :getExtendedClass
75
+
76
+ # Retrieve all registered custom classes.
77
+ #
78
+ # @return [Hash] Registry of custom classes
79
+ def getCustomClasses()
80
+ return @@custom_classes
81
+ end
82
+ module_function :getCustomClasses
83
+
84
+ # Set whether verbose log is enabled.
85
+ #
86
+ # @param val [Boolean] True to enable verbose logs
87
+ def setVerbose(val)
88
+ self.verbose_log = val
89
+ end
90
+ module_function :setVerbose
91
+
92
+ end
93
+
94
+ require_relative 'ci'
95
+ require_relative 'travis'
96
+ require_relative 'azure'
97
+ require_relative 'repo'
98
+ require_relative 'branch'
99
+
100
+ # Re-open the module to declare registry functions and load addons.
101
+ module GitMaintain
102
+ # Pre-declaration
103
+ class BranchIterator < Common; end
104
+
105
+ # Action classes supported by git-maintain CLI.
106
+ ACTION_CLASS = [ Common, BranchIterator, Repo ]
107
+
108
+ # Load all custom classes from the default addons directory
109
+ loadAddons(File.expand_path('addons', __dir__))
110
+
111
+ # Load any eventual user custom directory if specified
112
+ if ENV["GIT_MAINTAIN_ADDON_DIR"].to_s != ""
113
+ loadAddons(ENV["GIT_MAINTAIN_ADDON_DIR"].to_s)
114
+ end
115
+ end
116
+
117
+ require_relative 'branch_iterator'
@@ -0,0 +1,81 @@
1
+ module GitMaintain
2
+
3
+ # Base class for all GitMaintain exceptions
4
+ class GitMaintainError < RuntimeError
5
+ end
6
+
7
+ # Exception raised when cherry-pick is aborted by user
8
+ class CPAbort < GitMaintainError
9
+ end
10
+
11
+ # Exception raised when cherry-pick of a patch is skipped by user
12
+ class CPSkip < GitMaintainError
13
+ # Initialize a new SCPSkip error
14
+ # @param s [String] Message or patch info
15
+ def initialize(s="")
16
+ super("Skipping patch #{s}")
17
+ end
18
+ end
19
+
20
+ # Exception raised when a SHA is not found in the repository
21
+ class ShaNotFoundError < GitMaintainError
22
+ # Initialize a new ShaNotFoundError
23
+ # @param sha [String] The missing SHA
24
+ def initialize(sha)
25
+ super("SHA '#{sha}' was not found in the repository")
26
+ end
27
+ end
28
+
29
+ # Exception raised when a required argument is missing
30
+ class MissingArgumentError < GitMaintainError
31
+ # Initialize a new MissingArgumentError
32
+ # @param arg [String] The name of the missing argument
33
+ def initialize(arg)
34
+ super("Missing required argument: #{arg}")
35
+ end
36
+ end
37
+
38
+ # Exception raised when an argument is incorrect
39
+ class InvalidArgumentError < GitMaintainError
40
+ # Initialize a new InvalidArgumentError
41
+ # @param msg [String] Description of the invalid argument
42
+ def initialize(arg)
43
+ super("Invalid argument: #{arg}")
44
+ end
45
+ end
46
+
47
+ # Exception raised when a file is not found
48
+ class FileNotFoundError < GitMaintainError
49
+ # Initialize a new FileNotFoundError
50
+ # @param path [String] The path to the missing file
51
+ def initialize(path)
52
+ super("File not found: #{path}")
53
+ end
54
+ end
55
+
56
+ # Exception raised when a reference is not found in the repository
57
+ class NoRefError < GitMaintainError
58
+ def initialize(ref)
59
+ super("Reference '#{ref}' was not found")
60
+ end
61
+ end
62
+
63
+ # Exception raised when a cherry-pick fails
64
+ class CherryPickErrorException < GitMaintainError
65
+ def initialize(str, commit)
66
+ @commit = commit
67
+ super(str)
68
+ end
69
+ attr_reader :commit
70
+ end
71
+
72
+ # Exception raised when git-maintain is run outside of a git repository
73
+ class NotARepoError < GitMaintainError
74
+ # Initialize a new NotARepoError
75
+ # @param path [String] Path where repo was expected
76
+ def initialize(path)
77
+ super("'#{path}' is not a git repository")
78
+ end
79
+ end
80
+
81
+ end