sugarjar 1.1.3 → 2.0.1

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,35 @@
1
+ class SugarJar
2
+ class Commands
3
+ def feature(name, base = nil)
4
+ assert_in_repo!
5
+ SugarJar::Log.debug("Feature: #{name}, #{base}")
6
+ name = fprefix(name)
7
+ die("#{name} already exists!") if all_local_branches.include?(name)
8
+ if base
9
+ fbase = fprefix(base)
10
+ base = fbase if all_local_branches.include?(fbase)
11
+ else
12
+ base ||= most_main
13
+ end
14
+ # If our base is a local branch, don't try to parse it for a remote name
15
+ unless all_local_branches.include?(base)
16
+ base_pieces = base.split('/')
17
+ git('fetch', base_pieces[0]) if base_pieces.length > 1
18
+ end
19
+ git('checkout', '-b', name, base)
20
+ git('branch', '-u', base)
21
+ SugarJar::Log.info(
22
+ "Created feature branch #{color(name, :green)} based on " +
23
+ color(base, :green),
24
+ )
25
+ end
26
+ alias f feature
27
+
28
+ def subfeature(name)
29
+ assert_in_repo!
30
+ SugarJar::Log.debug("Subfature: #{name}")
31
+ feature(name, current_branch)
32
+ end
33
+ alias sf subfeature
34
+ end
35
+ end
@@ -0,0 +1,48 @@
1
+ require_relative '../util'
2
+
3
+ class SugarJar
4
+ class Commands
5
+ def pullsuggestions
6
+ assert_in_repo!
7
+
8
+ if dirty?
9
+ if @ignore_dirty
10
+ SugarJar::Log.warn(
11
+ 'Your repo is dirty, but --ignore-dirty was specified, so ' +
12
+ 'carrying on anyway.',
13
+ )
14
+ else
15
+ SugarJar::Log.error(
16
+ 'Your repo is dirty, so I am not going to push. Please commit ' +
17
+ 'or amend first.',
18
+ )
19
+ exit(1)
20
+ end
21
+ end
22
+
23
+ src = "origin/#{current_branch}"
24
+ fetch('origin')
25
+ diff = git('diff', "..#{src}").stdout
26
+ return unless diff && !diff.empty?
27
+
28
+ puts "Will merge the following suggestions:\n\n#{diff}"
29
+
30
+ loop do
31
+ $stdout.print("\nAre you sure? [y/n] ")
32
+ ans = $stdin.gets.strip
33
+ case ans
34
+ when /^[Yy]$/
35
+ git = SugarJar::Util.which('git')
36
+ system(git, 'merge', '--ff', "origin/#{current_branch}")
37
+ break
38
+ when /^[Nn]$/, /^[Qq](uit)?/
39
+ puts 'Not merging at user request...'
40
+ break
41
+ else
42
+ puts "Didn't understand '#{ans}'."
43
+ end
44
+ end
45
+ end
46
+ alias ps pullsuggestions
47
+ end
48
+ end
@@ -0,0 +1,66 @@
1
+ class SugarJar
2
+ class Commands
3
+ def smartpush(remote = nil, branch = nil)
4
+ assert_in_repo!
5
+ _smartpush(remote, branch, false)
6
+ end
7
+ alias spush smartpush
8
+
9
+ def forcepush(remote = nil, branch = nil)
10
+ assert_in_repo!
11
+ _smartpush(remote, branch, true)
12
+ end
13
+ alias fpush forcepush
14
+
15
+ private
16
+
17
+ def _smartpush(remote, branch, force)
18
+ unless remote && branch
19
+ remote ||= 'origin'
20
+ branch ||= current_branch
21
+ end
22
+
23
+ if dirty?
24
+ if @ignore_dirty
25
+ SugarJar::Log.warn(
26
+ 'Your repo is dirty, but --ignore-dirty was specified, so ' +
27
+ 'carrying on anyway.',
28
+ )
29
+ else
30
+ SugarJar::Log.error(
31
+ 'Your repo is dirty, so I am not going to push. Please commit ' +
32
+ 'or amend first.',
33
+ )
34
+ exit(1)
35
+ end
36
+ end
37
+
38
+ unless run_prepush
39
+ if @ignore_prerun_failure
40
+ SugarJar::Log.warn(
41
+ 'Pre-push checks failed, but --ignore-prerun-failure was ' +
42
+ 'specified, so carrying on anyway',
43
+ )
44
+ else
45
+ SugarJar::Log.error('Pre-push checks failed. Not pushing.')
46
+ exit(1)
47
+ end
48
+ end
49
+
50
+ args = ['push', remote, branch]
51
+ args << '--force-with-lease' if force
52
+ puts git(*args).stderr
53
+ end
54
+
55
+ def run_prepush
56
+ @repo_config['on_push']&.each do |item|
57
+ SugarJar::Log.debug("Running on_push check type #{item}")
58
+ unless run_check(item)
59
+ SugarJar::Log.info("[prepush]: #{item} #{color('failed', :red)}.")
60
+ return false
61
+ end
62
+ end
63
+ true
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,30 @@
1
+ class SugarJar
2
+ class Commands
3
+ def smartclone(repo, dir = nil, *)
4
+ reponame = File.basename(repo, '.git')
5
+ dir ||= reponame
6
+ org = extract_org(repo)
7
+
8
+ SugarJar::Log.info("Cloning #{reponame}...")
9
+
10
+ # GH's 'fork' command (with the --clone arg) will fork, if necessary,
11
+ # then clone, and then setup the remotes with the appropriate names. So
12
+ # we just let it do all the work for us and return.
13
+ #
14
+ # Unless the repo is in our own org and cannot be forked, then it
15
+ # will fail.
16
+ if org == @ghuser
17
+ git('clone', canonicalize_repo(repo), dir, *)
18
+ else
19
+ ghcli('repo', 'fork', '--clone', canonicalize_repo(repo), dir, *)
20
+ # make the main branch track upstream
21
+ Dir.chdir dir do
22
+ git('branch', '-u', "upstream/#{main_branch}")
23
+ end
24
+ end
25
+
26
+ SugarJar::Log.info('Remotes "origin" and "upstream" configured.')
27
+ end
28
+ alias sclone smartclone
29
+ end
30
+ end
@@ -0,0 +1,101 @@
1
+ require_relative '../util'
2
+
3
+ class SugarJar
4
+ class Commands
5
+ def smartpullrequest(*args)
6
+ assert_in_repo!
7
+ assert_common_main_branch!
8
+
9
+ if dirty?
10
+ SugarJar::Log.warn(
11
+ 'Your repo is dirty, so I am not going to create a pull request. ' +
12
+ 'You should commit or amend and push it to your remote first.',
13
+ )
14
+ exit(1)
15
+ end
16
+
17
+ user_specified_base = args.include?('-B') || args.include?('--base')
18
+
19
+ curr = current_branch
20
+ base = tracked_branch
21
+ if @pr_autofill
22
+ SugarJar::Log.info('Autofilling in PR from commit message')
23
+ num_commits = git(
24
+ 'rev-list', '--count', curr, "^#{base}"
25
+ ).stdout.strip.to_i
26
+ if num_commits > 1
27
+ args.unshift('--fill-first')
28
+ else
29
+ args.unshift('--fill')
30
+ end
31
+ end
32
+ unless user_specified_base
33
+ if subfeature?(base)
34
+ if upstream_org != push_org
35
+ SugarJar::Log.warn(
36
+ 'Unfortunately you cannot based one PR on another PR when' +
37
+ " using fork-based PRs. We will base this on #{most_main}." +
38
+ ' This just means the PR "Changes" tab will show changes for' +
39
+ ' the full stack until those other PRs are merged and this PR' +
40
+ ' PR is rebased.',
41
+ )
42
+ # nil is prompt, true is always, false is never
43
+ elsif @pr_autostack.nil?
44
+ $stdout.print(
45
+ 'It looks like this is a subfeature, would you like to base ' +
46
+ "this PR on #{base}? [y/n] ",
47
+ )
48
+ ans = $stdin.gets.strip
49
+ args.unshift('--base', base) if %w{Y y}.include?(ans)
50
+ elsif @pr_autostack
51
+ args.unshift('--base', base)
52
+ end
53
+ elsif base.include?('/') && base != most_main
54
+ # If our base is a remote branch, then use that as the
55
+ # base branch of the PR
56
+ args.unshift('--base', base.split('/').last)
57
+ end
58
+ end
59
+
60
+ # <org>:<branch> is the GH API syntax for:
61
+ # look for a branch of name <branch>, from a fork in owner <org>
62
+ args.unshift('--head', "#{push_org}:#{curr}")
63
+ SugarJar::Log.trace("Running: gh pr create #{args.join(' ')}")
64
+ gh = SugarJar::Util.which('gh')
65
+ system(gh, 'pr', 'create', *args)
66
+ end
67
+
68
+ alias spr smartpullrequest
69
+ alias smartpr smartpullrequest
70
+
71
+ private
72
+
73
+ def assert_common_main_branch!
74
+ upstream_branch = main_remote_branch(upstream)
75
+ unless main_branch == upstream_branch
76
+ die(
77
+ "The local main branch is '#{main_branch}', but the main branch " +
78
+ "of the #{upstream} remote is '#{upstream_branch}'. You probably " +
79
+ "want to rename your local branch by doing:\n\t" +
80
+ "git branch -m #{main_branch} #{upstream_branch}\n\t" +
81
+ "git fetch #{upstream}\n\t" +
82
+ "git branch -u #{upstream}/#{upstream_branch} #{upstream_branch}\n" +
83
+ "\tgit remote set-head #{upstream} -a",
84
+ )
85
+ end
86
+ return if upstream_branch == 'origin'
87
+
88
+ origin_branch = main_remote_branch('origin')
89
+ return if origin_branch == upstream_branch
90
+
91
+ die(
92
+ "The main branch of your upstream (#{upstream_branch}) and your " +
93
+ "fork/origin (#{origin_branch}) are not the same. You should go " +
94
+ "to https://#{@ghhost || 'github.com'}/#{@ghuser}/#{repo_name}/" +
95
+ 'branches/ and rename the \'default\' branch to ' +
96
+ "'#{upstream_branch}'. It will then give you some commands to " +
97
+ 'run to update this clone.',
98
+ )
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,103 @@
1
+ class SugarJar
2
+ class Commands
3
+ def up(branch = nil)
4
+ assert_in_repo!
5
+ branch ||= current_branch
6
+ branch = fprefix(branch)
7
+ # get a copy of our current branch, if rebase fails, we won't
8
+ # be able to determine it without backing out
9
+ curr = current_branch
10
+ git('checkout', branch)
11
+ result = rebase
12
+ if result['so'].error?
13
+ backout = ''
14
+ if rebase_in_progress?
15
+ backout = ' You can get out of this with a `git rebase --abort`.'
16
+ end
17
+
18
+ die(
19
+ "#{color(curr, :red)}: Failed to rebase on " +
20
+ "#{result['base']}. Leaving the repo as-is.#{backout} " +
21
+ 'Output from failed rebase is: ' +
22
+ "\nSTDOUT:\n#{result['so'].stdout.lines.map { |x| "\t#{x}" }.join}" +
23
+ "\nSTDERR:\n#{result['so'].stderr.lines.map { |x| "\t#{x}" }.join}",
24
+ )
25
+ else
26
+ SugarJar::Log.info(
27
+ "#{color(current_branch, :green)} rebased on #{result['base']}",
28
+ )
29
+ # go back to where we were if we rebased a different branch
30
+ git('checkout', curr) if branch != curr
31
+ end
32
+ end
33
+
34
+ def upall
35
+ assert_in_repo!
36
+ all_local_branches.each do |branch|
37
+ next if MAIN_BRANCHES.include?(branch)
38
+
39
+ git('checkout', branch)
40
+ result = rebase
41
+ if result['so'].error?
42
+ SugarJar::Log.error(
43
+ "#{color(branch, :red)} failed rebase. Reverting attempt and " +
44
+ 'moving to next branch. Try `sj up` manually on that branch.',
45
+ )
46
+ git('rebase', '--abort') if rebase_in_progress?
47
+ else
48
+ SugarJar::Log.info(
49
+ "#{color(branch, :green)} rebased on " +
50
+ color(result['base'], :green).to_s,
51
+ )
52
+ end
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def rebase
59
+ SugarJar::Log.debug('Fetching upstream')
60
+ fetch_upstream
61
+ curr = current_branch
62
+ # this isn't a hash, it's a named param, silly rubocop
63
+ # rubocop:disable Style/HashSyntax
64
+ base = tracked_branch(fallback: false)
65
+ # rubocop:enable Style/HashSyntax
66
+ unless base
67
+ SugarJar::Log.info(
68
+ 'The brach we were tracking is gone, resetting tracking to ' +
69
+ most_main,
70
+ )
71
+ git('branch', '-u', most_main)
72
+ base = most_main
73
+ end
74
+ # If this is a subfeature based on a local branch which has since
75
+ # been deleted, 'tracked branch' will automatically return <most_main>
76
+ # so we don't need any special handling for that
77
+ if !MAIN_BRANCHES.include?(curr) && base == "origin/#{curr}"
78
+ SugarJar::Log.warn(
79
+ "This branch is tracking origin/#{curr}, which is probably your " +
80
+ 'downstream (where you push _to_) as opposed to your upstream ' +
81
+ '(where you pull _from_). This means that "sj up" is probably ' +
82
+ 'rebasing on the wrong thing and doing nothing. You probably want ' +
83
+ "to do a 'git branch -u #{most_main}'.",
84
+ )
85
+ end
86
+ SugarJar::Log.debug('Rebasing')
87
+ s = git_nofail('rebase', base)
88
+ {
89
+ 'so' => s,
90
+ 'base' => base,
91
+ }
92
+ end
93
+
94
+ def rebase_in_progress?
95
+ # for rebase without -i
96
+ rebase_file = git('rev-parse', '--git-path', 'rebase-apply').stdout.strip
97
+ # for rebase -i
98
+ rebase_merge_file = git('rev-parse', '--git-path', 'rebase-merge').
99
+ stdout.strip
100
+ File.exist?(rebase_file) || File.exist?(rebase_merge_file)
101
+ end
102
+ end
103
+ end