sugarjar 2.0.1 → 3.0.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 +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +151 -55
- data/bin/sj +96 -23
- data/examples/sample_config.yaml +6 -0
- data/examples/sample_repoconfig.yaml +19 -3
- data/lib/sugarjar/commands/bclean.rb +147 -17
- data/lib/sugarjar/commands/checks.rb +27 -8
- data/lib/sugarjar/commands/debuginfo.rb +1 -1
- data/lib/sugarjar/commands/feature.rb +25 -2
- data/lib/sugarjar/commands/pullsuggestions.rb +2 -15
- data/lib/sugarjar/commands/push.rb +4 -15
- data/lib/sugarjar/commands/smartclone.rb +47 -3
- data/lib/sugarjar/commands/smartpullrequest.rb +27 -5
- data/lib/sugarjar/commands/up.rb +57 -4
- data/lib/sugarjar/commands.rb +155 -25
- data/lib/sugarjar/config.rb +32 -4
- data/lib/sugarjar/log.rb +2 -0
- data/lib/sugarjar/util.rb +28 -14
- data/lib/sugarjar/version.rb +1 -1
- metadata +2 -2
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
class SugarJar
|
|
2
2
|
class Commands
|
|
3
|
-
def
|
|
3
|
+
def lbclean(name = nil)
|
|
4
4
|
assert_in_repo!
|
|
5
5
|
name ||= current_branch
|
|
6
6
|
name = fprefix(name)
|
|
7
|
+
|
|
8
|
+
should_skip, why = skip_branch_info(name)
|
|
9
|
+
if should_skip
|
|
10
|
+
msg = "#{name}: #{color('skipped', :yellow)}"
|
|
11
|
+
msg << " (#{why})" if why
|
|
12
|
+
SugarJar::Log.warn(msg)
|
|
13
|
+
return
|
|
14
|
+
end
|
|
15
|
+
|
|
7
16
|
if clean_branch(name)
|
|
8
17
|
SugarJar::Log.info("#{name}: #{color('reaped', :green)}")
|
|
9
18
|
else
|
|
@@ -13,13 +22,57 @@ class SugarJar
|
|
|
13
22
|
)
|
|
14
23
|
end
|
|
15
24
|
end
|
|
25
|
+
alias localbranchclean lbclean
|
|
26
|
+
# backcompat
|
|
27
|
+
alias bclean lbclean
|
|
28
|
+
|
|
29
|
+
def rbclean(name = nil, remote = nil)
|
|
30
|
+
assert_in_repo!
|
|
31
|
+
name ||= current_branch
|
|
32
|
+
name = fprefix(name)
|
|
33
|
+
remote ||= 'origin'
|
|
34
|
+
|
|
35
|
+
ref = "refs/remotes/#{remote}/#{name}"
|
|
36
|
+
if git_nofail('show-ref', '--quiet', ref).error?
|
|
37
|
+
SugarJar::Log.warn("Remote branch #{name} on #{remote} does not exist.")
|
|
38
|
+
return
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if clean_branch(ref, :remote)
|
|
42
|
+
SugarJar::Log.info("#{ref}: #{color('reaped', :green)}")
|
|
43
|
+
else
|
|
44
|
+
die(
|
|
45
|
+
"#{color("Cannot clean #{ref}", :red)}! there are unmerged " +
|
|
46
|
+
"commits; use 'git push #{remote} -d #{name}' to forcefully delete " +
|
|
47
|
+
' it.',
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
alias remotebranchclean rbclean
|
|
16
52
|
|
|
17
|
-
def
|
|
53
|
+
def gbclean(name = nil, remote = nil)
|
|
54
|
+
assert_in_repo!
|
|
55
|
+
name ||= current_branch
|
|
56
|
+
remote ||= 'origin'
|
|
57
|
+
lbclean(name)
|
|
58
|
+
rbclean(name, remote)
|
|
59
|
+
end
|
|
60
|
+
alias globalbranchclean gbclean
|
|
61
|
+
|
|
62
|
+
def lbcleanall
|
|
18
63
|
assert_in_repo!
|
|
19
64
|
curr = current_branch
|
|
65
|
+
worktree_branches
|
|
20
66
|
all_local_branches.each do |branch|
|
|
21
|
-
|
|
22
|
-
|
|
67
|
+
# skip_branch info will check for MAIN_BRANCHES, but we
|
|
68
|
+
# quietly skip them.
|
|
69
|
+
next if MAIN_BRANCHES.include?(branch)
|
|
70
|
+
|
|
71
|
+
should_skip, why = skip_branch_info(branch)
|
|
72
|
+
if should_skip
|
|
73
|
+
msg = "#{branch}: #{color('skipped', :yellow)}"
|
|
74
|
+
msg << " (#{why})" if why
|
|
75
|
+
SugarJar::Log.info(msg)
|
|
23
76
|
next
|
|
24
77
|
end
|
|
25
78
|
|
|
@@ -41,28 +94,87 @@ class SugarJar
|
|
|
41
94
|
checkout_main_branch
|
|
42
95
|
end
|
|
43
96
|
end
|
|
97
|
+
alias localbranchcleanall lbcleanall
|
|
98
|
+
# backcomat
|
|
99
|
+
alias bcleanall lbcleanall
|
|
100
|
+
|
|
101
|
+
def rbcleanall(remote = nil)
|
|
102
|
+
assert_in_repo!
|
|
103
|
+
curr = current_branch
|
|
104
|
+
remote ||= 'origin'
|
|
105
|
+
all_remote_branches(remote).each do |branch|
|
|
106
|
+
if (MAIN_BRANCHES + ['HEAD']).include?(branch)
|
|
107
|
+
SugarJar::Log.debug("Skipping #{branch}")
|
|
108
|
+
next
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
ref = "refs/remotes/#{remote}/#{branch}"
|
|
112
|
+
if clean_branch(ref, :remote)
|
|
113
|
+
SugarJar::Log.info("#{ref}: #{color('reaped', :green)}")
|
|
114
|
+
else
|
|
115
|
+
SugarJar::Log.info("#{ref}: skipped")
|
|
116
|
+
SugarJar::Log.debug(
|
|
117
|
+
"There are unmerged commits; use 'git branch -D #{branch}' to " +
|
|
118
|
+
'forcefully delete it)',
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Return to the branch we were on, or main
|
|
124
|
+
if all_local_branches.include?(curr)
|
|
125
|
+
git('checkout', curr)
|
|
126
|
+
else
|
|
127
|
+
checkout_main_branch
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
alias remotebranchcleanall rbcleanall
|
|
131
|
+
|
|
132
|
+
def gbcleanall(remote = nil)
|
|
133
|
+
assert_in_repo!
|
|
134
|
+
bcleanall
|
|
135
|
+
rcleanall(remote)
|
|
136
|
+
end
|
|
137
|
+
alias globalbranchcleanall gbcleanall
|
|
44
138
|
|
|
45
139
|
private
|
|
46
140
|
|
|
47
|
-
|
|
48
|
-
|
|
141
|
+
# rubocop:disable Naming/PredicateMethod
|
|
142
|
+
def clean_branch(name, type = :local)
|
|
143
|
+
undeleteable = MAIN_BRANCHES.dup
|
|
144
|
+
undeleteable << 'HEAD' if type == :remote
|
|
145
|
+
die("Cannot remove #{name} branch") if undeleteable.include?(name)
|
|
49
146
|
SugarJar::Log.debug('Fetch relevant remote...')
|
|
50
147
|
fetch_upstream
|
|
51
|
-
|
|
148
|
+
fetch(remote_from_ref(name)) if type == :remote
|
|
149
|
+
return false unless safe_to_clean?(name)
|
|
52
150
|
|
|
53
151
|
SugarJar::Log.debug('branch deemed safe to delete...')
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
152
|
+
if type == :remote
|
|
153
|
+
remote = remote_from_ref(name)
|
|
154
|
+
branch = branch_from_ref(name, :remote)
|
|
155
|
+
git('push', remote, '--delete', branch)
|
|
156
|
+
else
|
|
157
|
+
checkout_main_branch
|
|
158
|
+
git('branch', '-D', name)
|
|
159
|
+
rebase
|
|
160
|
+
end
|
|
57
161
|
true
|
|
58
162
|
end
|
|
163
|
+
# rubocop:enable Naming/PredicateMethod
|
|
59
164
|
|
|
60
|
-
def safe_to_clean(branch)
|
|
165
|
+
def safe_to_clean?(branch)
|
|
61
166
|
# cherry -v will output 1 line per commit on the target branch
|
|
62
167
|
# prefixed by a - or + - anything with a - can be dropped, anything
|
|
63
168
|
# else cannot.
|
|
169
|
+
SugarJar::Log.debug("Checking if branch #{branch} is safe to delete...")
|
|
170
|
+
if branch.start_with?('refs/remotes/')
|
|
171
|
+
remote = remote_from_ref(branch)
|
|
172
|
+
tracked = main_remote_branch(remote)
|
|
173
|
+
else
|
|
174
|
+
tracked = tracked_branch(branch)
|
|
175
|
+
end
|
|
64
176
|
out = git(
|
|
65
|
-
'cherry', '-v',
|
|
177
|
+
'cherry', '-v', tracked, branch
|
|
66
178
|
).stdout.lines.reject do |line|
|
|
67
179
|
line.start_with?('-')
|
|
68
180
|
end
|
|
@@ -80,10 +192,10 @@ class SugarJar
|
|
|
80
192
|
# First we need a temp branch to work on
|
|
81
193
|
tmpbranch = "_sugar_jar.#{Process.pid}"
|
|
82
194
|
|
|
83
|
-
git('checkout', '-b', tmpbranch,
|
|
195
|
+
git('checkout', '-b', tmpbranch, tracked)
|
|
84
196
|
s = git_nofail('merge', '--squash', branch)
|
|
85
197
|
if s.error?
|
|
86
|
-
cleanup_tmp_branch(tmpbranch, branch)
|
|
198
|
+
cleanup_tmp_branch(tmpbranch, branch, tracked)
|
|
87
199
|
SugarJar::Log.debug(
|
|
88
200
|
'Failed to merge changes into current main. This means we could ' +
|
|
89
201
|
'not figure out if this is merged or not. Check manually and use ' +
|
|
@@ -95,7 +207,7 @@ class SugarJar
|
|
|
95
207
|
s = git('diff', '--staged')
|
|
96
208
|
out = s.stdout
|
|
97
209
|
SugarJar::Log.debug("Squash-merged diff: #{out}")
|
|
98
|
-
cleanup_tmp_branch(tmpbranch, branch)
|
|
210
|
+
cleanup_tmp_branch(tmpbranch, branch, tracked)
|
|
99
211
|
if out.empty?
|
|
100
212
|
SugarJar::Log.debug(
|
|
101
213
|
'After squash-merging, this branch appears safe to delete',
|
|
@@ -109,10 +221,28 @@ class SugarJar
|
|
|
109
221
|
end
|
|
110
222
|
end
|
|
111
223
|
|
|
112
|
-
def cleanup_tmp_branch(tmp, backto)
|
|
113
|
-
|
|
224
|
+
def cleanup_tmp_branch(tmp, backto, tracked = nil)
|
|
225
|
+
tracked ||= tracked_branch
|
|
226
|
+
# Reset any changes on our temp branch from various merge attempts
|
|
227
|
+
# so we're in a state we know we can 'checkout' away from.
|
|
228
|
+
git('reset', '--hard', tracked)
|
|
229
|
+
# checkout whatever branch we were on before
|
|
114
230
|
git('checkout', backto)
|
|
231
|
+
# delete our temp branch
|
|
115
232
|
git('branch', '-D', tmp)
|
|
116
233
|
end
|
|
234
|
+
|
|
235
|
+
def skip_branch_info(name)
|
|
236
|
+
return true, 'main branch' if MAIN_BRANCHES.include?(name)
|
|
237
|
+
|
|
238
|
+
wt_branches = worktree_branches
|
|
239
|
+
rel_branches = release_branches
|
|
240
|
+
|
|
241
|
+
return true, 'worktree' if wt_branches.include?(name)
|
|
242
|
+
|
|
243
|
+
return true, 'release branch' if rel_branches.include?(name)
|
|
244
|
+
|
|
245
|
+
[false, nil]
|
|
246
|
+
end
|
|
117
247
|
end
|
|
118
248
|
end
|
|
@@ -4,6 +4,8 @@ class SugarJar
|
|
|
4
4
|
class Commands
|
|
5
5
|
def lint
|
|
6
6
|
assert_in_repo!
|
|
7
|
+
|
|
8
|
+
# does not use dirty_check! as we want a custom message
|
|
7
9
|
if dirty?
|
|
8
10
|
if @ignore_dirty
|
|
9
11
|
SugarJar::Log.warn(
|
|
@@ -20,12 +22,12 @@ class SugarJar
|
|
|
20
22
|
exit(1)
|
|
21
23
|
end
|
|
22
24
|
end
|
|
23
|
-
exit(1) unless run_check('lint')
|
|
25
|
+
exit(1) unless run_check('lint', false)
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
def unit
|
|
27
29
|
assert_in_repo!
|
|
28
|
-
exit(1) unless run_check('unit')
|
|
30
|
+
exit(1) unless run_check('unit', false)
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
def get_checks_from_command(type)
|
|
@@ -69,7 +71,13 @@ class SugarJar
|
|
|
69
71
|
@checks[type]
|
|
70
72
|
end
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
# autorun is true when we're running from push, and false when someone
|
|
75
|
+
# ran 'lint' or 'unit' directly
|
|
76
|
+
#
|
|
77
|
+
# In the case of a autorun, if a linter changes the code, we require
|
|
78
|
+
# either the user amend, or bail out. If it's a manual run, then we
|
|
79
|
+
# allow them to just go on.
|
|
80
|
+
def run_check(type, autorun)
|
|
73
81
|
repo_root = SugarJar::Util.repo_root
|
|
74
82
|
Dir.chdir repo_root do
|
|
75
83
|
checks = get_checks(type)
|
|
@@ -79,6 +87,7 @@ class SugarJar
|
|
|
79
87
|
|
|
80
88
|
checks.each do |check|
|
|
81
89
|
SugarJar::Log.debug("Running #{type} #{check}")
|
|
90
|
+
skip_redo = false
|
|
82
91
|
|
|
83
92
|
short = check.split.first
|
|
84
93
|
if short.include?('/')
|
|
@@ -102,10 +111,15 @@ class SugarJar
|
|
|
102
111
|
)
|
|
103
112
|
puts git('diff').stdout
|
|
104
113
|
loop do
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
options = [
|
|
115
|
+
'[q]uit and inspect',
|
|
116
|
+
'[a]mend the changes to the current commit and re-run',
|
|
117
|
+
]
|
|
118
|
+
options << '[i]gnore the changes and keep going' unless autorun
|
|
119
|
+
|
|
120
|
+
msg = "\nWould you like to\n\t" + options.join("\n\t") + "\n > "
|
|
121
|
+
|
|
122
|
+
$stdout.print(msg)
|
|
109
123
|
ans = $stdin.gets.strip
|
|
110
124
|
case ans
|
|
111
125
|
when /^q/
|
|
@@ -116,9 +130,14 @@ class SugarJar
|
|
|
116
130
|
# break here, if we get out of this loop we 'redo', assuming
|
|
117
131
|
# the user chose this option
|
|
118
132
|
break
|
|
133
|
+
when /^i/
|
|
134
|
+
unless autorun
|
|
135
|
+
skip_redo = true
|
|
136
|
+
break
|
|
137
|
+
end
|
|
119
138
|
end
|
|
120
139
|
end
|
|
121
|
-
redo
|
|
140
|
+
redo unless skip_redo
|
|
122
141
|
end
|
|
123
142
|
|
|
124
143
|
if s.error?
|
|
@@ -5,10 +5,32 @@ class SugarJar
|
|
|
5
5
|
SugarJar::Log.debug("Feature: #{name}, #{base}")
|
|
6
6
|
name = fprefix(name)
|
|
7
7
|
die("#{name} already exists!") if all_local_branches.include?(name)
|
|
8
|
+
rel_branches = release_branches
|
|
8
9
|
if base
|
|
9
|
-
|
|
10
|
-
base
|
|
10
|
+
# If the user specified a base branch (sf mything base)
|
|
11
|
+
# we check if <base> is a release branch and if so, we make
|
|
12
|
+
# this track <upstream>/<base>
|
|
13
|
+
if rel_branches.include?(base)
|
|
14
|
+
newbase = "#{upstream}/#{base}"
|
|
15
|
+
SugarJar::Log.info(
|
|
16
|
+
"Base branch #{base} is a release branch, setting it to track " +
|
|
17
|
+
newbase,
|
|
18
|
+
)
|
|
19
|
+
base = newbase
|
|
20
|
+
else
|
|
21
|
+
fbase = fprefix(base)
|
|
22
|
+
base = fbase if all_local_branches.include?(fbase)
|
|
23
|
+
end
|
|
24
|
+
elsif rel_branches.include?(name)
|
|
25
|
+
# If the user did NOT specify a base *and* this new feature is
|
|
26
|
+
# a release branch, check it out tracking the upstream release
|
|
27
|
+
# branch instead of main
|
|
28
|
+
base = "#{upstream}/#{name}"
|
|
29
|
+
SugarJar::Log.info(
|
|
30
|
+
"Feature #{name} is a release branch, setting it to track #{base}",
|
|
31
|
+
)
|
|
11
32
|
else
|
|
33
|
+
# otherwise, fallback to most-main
|
|
12
34
|
base ||= most_main
|
|
13
35
|
end
|
|
14
36
|
# If our base is a local branch, don't try to parse it for a remote name
|
|
@@ -25,6 +47,7 @@ class SugarJar
|
|
|
25
47
|
end
|
|
26
48
|
alias f feature
|
|
27
49
|
|
|
50
|
+
# alias for "feature <current_branch>'
|
|
28
51
|
def subfeature(name)
|
|
29
52
|
assert_in_repo!
|
|
30
53
|
SugarJar::Log.debug("Subfature: #{name}")
|
|
@@ -4,24 +4,11 @@ class SugarJar
|
|
|
4
4
|
class Commands
|
|
5
5
|
def pullsuggestions
|
|
6
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
|
|
7
|
+
dirty_check!
|
|
22
8
|
|
|
23
9
|
src = "origin/#{current_branch}"
|
|
24
10
|
fetch('origin')
|
|
11
|
+
|
|
25
12
|
diff = git('diff', "..#{src}").stdout
|
|
26
13
|
return unless diff && !diff.empty?
|
|
27
14
|
|
|
@@ -20,20 +20,7 @@ class SugarJar
|
|
|
20
20
|
branch ||= current_branch
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
|
|
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
|
|
23
|
+
dirty_check!
|
|
37
24
|
|
|
38
25
|
unless run_prepush
|
|
39
26
|
if @ignore_prerun_failure
|
|
@@ -52,15 +39,17 @@ class SugarJar
|
|
|
52
39
|
puts git(*args).stderr
|
|
53
40
|
end
|
|
54
41
|
|
|
42
|
+
# rubocop:disable Naming/PredicateMethod
|
|
55
43
|
def run_prepush
|
|
56
44
|
@repo_config['on_push']&.each do |item|
|
|
57
45
|
SugarJar::Log.debug("Running on_push check type #{item}")
|
|
58
|
-
unless run_check(item)
|
|
46
|
+
unless run_check(item, true)
|
|
59
47
|
SugarJar::Log.info("[prepush]: #{item} #{color('failed', :red)}.")
|
|
60
48
|
return false
|
|
61
49
|
end
|
|
62
50
|
end
|
|
63
51
|
true
|
|
64
52
|
end
|
|
53
|
+
# rubocop:enable Naming/PredicateMethod
|
|
65
54
|
end
|
|
66
55
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
class SugarJar
|
|
2
2
|
class Commands
|
|
3
3
|
def smartclone(repo, dir = nil, *)
|
|
4
|
-
reponame =
|
|
4
|
+
reponame = extract_repo(repo)
|
|
5
5
|
dir ||= reponame
|
|
6
6
|
org = extract_org(repo)
|
|
7
7
|
|
|
@@ -13,10 +13,15 @@ class SugarJar
|
|
|
13
13
|
#
|
|
14
14
|
# Unless the repo is in our own org and cannot be forked, then it
|
|
15
15
|
# will fail.
|
|
16
|
-
if org == @
|
|
16
|
+
if org == @forge_user
|
|
17
17
|
git('clone', canonicalize_repo(repo), dir, *)
|
|
18
18
|
else
|
|
19
|
-
|
|
19
|
+
if @repo_forge == 'gitlab'
|
|
20
|
+
_gitlab_clone(org, repo, dir, *)
|
|
21
|
+
else
|
|
22
|
+
forge('repo', 'fork', '--clone', canonicalize_repo(repo), dir, *)
|
|
23
|
+
end
|
|
24
|
+
|
|
20
25
|
# make the main branch track upstream
|
|
21
26
|
Dir.chdir dir do
|
|
22
27
|
git('branch', '-u', "upstream/#{main_branch}")
|
|
@@ -26,5 +31,44 @@ class SugarJar
|
|
|
26
31
|
SugarJar::Log.info('Remotes "origin" and "upstream" configured.')
|
|
27
32
|
end
|
|
28
33
|
alias sclone smartclone
|
|
34
|
+
|
|
35
|
+
def _gitlab_clone(_org, repo, dir, *)
|
|
36
|
+
# The gitlab CLI is much less forgiving about already-forked
|
|
37
|
+
# repos, and it has no option to clone to a differently-named
|
|
38
|
+
# directory. So we have to special case it.
|
|
39
|
+
|
|
40
|
+
# glab requires a short-name for the fork command...
|
|
41
|
+
shortname = repo_shortname(repo)
|
|
42
|
+
|
|
43
|
+
# We call fork without --clone since --clone can't clone
|
|
44
|
+
# to another directory. Also, we must specify =false, or it
|
|
45
|
+
# will prompt
|
|
46
|
+
s = forge_nofail('repo', 'fork', shortname, '--clone=false')
|
|
47
|
+
|
|
48
|
+
# It fails with:
|
|
49
|
+
# 409 {message: [Project namespace name has already been taken,
|
|
50
|
+
# Name has already been taken, Path has already been taken]}
|
|
51
|
+
#
|
|
52
|
+
# when there's already a fork... or if you happen to have a name
|
|
53
|
+
# collision. There's no way to tell, so we assume it means we've
|
|
54
|
+
# already forked.
|
|
55
|
+
if s.error?
|
|
56
|
+
if s.stderr.include?(' 409 ')
|
|
57
|
+
SugarJar::Log.debug('Forking failed, probably already forked')
|
|
58
|
+
else
|
|
59
|
+
s.error!
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Now we clone ourselves...
|
|
64
|
+
git('clone', canonicalize_repo(repo), dir, *)
|
|
65
|
+
Dir.chdir dir do
|
|
66
|
+
# and then configure remotes properly
|
|
67
|
+
git('remote', 'rename', 'origin', 'upstream')
|
|
68
|
+
|
|
69
|
+
fork_url = forked_repo(repo, @forge_user)
|
|
70
|
+
git('remote', 'add', 'origin', fork_url)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
29
73
|
end
|
|
30
74
|
end
|
|
@@ -6,6 +6,7 @@ class SugarJar
|
|
|
6
6
|
assert_in_repo!
|
|
7
7
|
assert_common_main_branch!
|
|
8
8
|
|
|
9
|
+
# does not use `dirty_check!` because we don't allow overriding here
|
|
9
10
|
if dirty?
|
|
10
11
|
SugarJar::Log.warn(
|
|
11
12
|
'Your repo is dirty, so I am not going to create a pull request. ' +
|
|
@@ -59,10 +60,25 @@ class SugarJar
|
|
|
59
60
|
|
|
60
61
|
# <org>:<branch> is the GH API syntax for:
|
|
61
62
|
# look for a branch of name <branch>, from a fork in owner <org>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
if @repo_forge == 'github'
|
|
64
|
+
# On GitHub, the head is the org and the *BRANCH* name to use as
|
|
65
|
+
# the head branch...
|
|
66
|
+
args.unshift('--head', "#{push_org}:#{curr}")
|
|
67
|
+
else
|
|
68
|
+
# On GitLab, the head is the repo (org/repo) to use as the head
|
|
69
|
+
# _repo_, and then branch is configured seperately (with -s), but
|
|
70
|
+
# we don't need that since it defaults to the local branch name.
|
|
71
|
+
#
|
|
72
|
+
# Then we need --yes for it to not prompt us
|
|
73
|
+
args.unshift('--head', "#{push_org}/#{reponame}", '--yes')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
bin = SugarJar::Util.which(_forge_cmd)
|
|
77
|
+
subcmd = _pr_cmd
|
|
78
|
+
SugarJar::Log.trace(
|
|
79
|
+
"Running: #{bin} #{subcmd} create #{args.join(' ')}",
|
|
80
|
+
)
|
|
81
|
+
system(bin, subcmd, 'create', *args)
|
|
66
82
|
end
|
|
67
83
|
|
|
68
84
|
alias spr smartpullrequest
|
|
@@ -70,6 +86,10 @@ class SugarJar
|
|
|
70
86
|
|
|
71
87
|
private
|
|
72
88
|
|
|
89
|
+
def _pr_cmd
|
|
90
|
+
@repo_forge == 'gitlab' ? 'mr' : 'pr'
|
|
91
|
+
end
|
|
92
|
+
|
|
73
93
|
def assert_common_main_branch!
|
|
74
94
|
upstream_branch = main_remote_branch(upstream)
|
|
75
95
|
unless main_branch == upstream_branch
|
|
@@ -86,7 +106,9 @@ class SugarJar
|
|
|
86
106
|
return if upstream_branch == 'origin'
|
|
87
107
|
|
|
88
108
|
origin_branch = main_remote_branch('origin')
|
|
89
|
-
|
|
109
|
+
# NOTE: that on GL, forks don't fork any branches by default, even
|
|
110
|
+
# a main one, so if it's 'nil', then ignore.
|
|
111
|
+
return if origin_branch.nil? || origin_branch == upstream_branch
|
|
90
112
|
|
|
91
113
|
die(
|
|
92
114
|
"The main branch of your upstream (#{upstream_branch}) and your " +
|
data/lib/sugarjar/commands/up.rb
CHANGED
|
@@ -53,19 +53,71 @@ class SugarJar
|
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
def fsync
|
|
57
|
+
sync(:force => true)
|
|
58
|
+
end
|
|
59
|
+
alias forcesync fsync
|
|
60
|
+
|
|
61
|
+
def sync(force: false)
|
|
62
|
+
assert_in_repo!
|
|
63
|
+
dirty_check!
|
|
64
|
+
|
|
65
|
+
src = "origin/#{current_branch}"
|
|
66
|
+
fetch('origin')
|
|
67
|
+
want_reset = false
|
|
68
|
+
if force
|
|
69
|
+
SugarJar::Log.debug('Forcing reset instead of rebase at user request')
|
|
70
|
+
want_reset = true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
unless force
|
|
74
|
+
s = git_nofail('merge-base', '--is-ancestor', 'HEAD', src)
|
|
75
|
+
# if this IS an ancestor, we can just force reset.
|
|
76
|
+
#
|
|
77
|
+
# otherwise, we attempt a rebase to not lose anything (unless
|
|
78
|
+
# force is set)
|
|
79
|
+
if s.error?
|
|
80
|
+
SugarJar::Log.debug(
|
|
81
|
+
"Choosing rebase sync since this isn't a direct ancestor",
|
|
82
|
+
)
|
|
83
|
+
else
|
|
84
|
+
SugarJar::Log.debug('Choosing reset sync since this is an ancestor')
|
|
85
|
+
want_reset = true
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if want_reset
|
|
90
|
+
git('reset', '--hard', src)
|
|
91
|
+
else
|
|
92
|
+
rebase(src)
|
|
93
|
+
s = git_nofail('rev-parse', '--verify', 'REBASE_HEAD')
|
|
94
|
+
unless s.error?
|
|
95
|
+
SugarJar::Log.info(
|
|
96
|
+
'Rebase required input. You may continue the rebase from' +
|
|
97
|
+
' here normally, or you may abort (`git rebase --abort`)' +
|
|
98
|
+
' and instead to `sj fsync` to skip a rebase and force' +
|
|
99
|
+
' reset to the remote branch.',
|
|
100
|
+
)
|
|
101
|
+
return
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
SugarJar::Log.info("Synced to #{src}.")
|
|
105
|
+
end
|
|
106
|
+
|
|
56
107
|
private
|
|
57
108
|
|
|
58
|
-
def rebase
|
|
109
|
+
def rebase(base = nil)
|
|
110
|
+
skip_base_warning = !base.nil?
|
|
59
111
|
SugarJar::Log.debug('Fetching upstream')
|
|
60
112
|
fetch_upstream
|
|
61
113
|
curr = current_branch
|
|
62
114
|
# this isn't a hash, it's a named param, silly rubocop
|
|
63
115
|
# rubocop:disable Style/HashSyntax
|
|
64
|
-
base
|
|
116
|
+
base ||= tracked_branch(fallback: false)
|
|
65
117
|
# rubocop:enable Style/HashSyntax
|
|
66
118
|
unless base
|
|
67
119
|
SugarJar::Log.info(
|
|
68
|
-
'The
|
|
120
|
+
'The branch we were tracking is gone, resetting tracking to ' +
|
|
69
121
|
most_main,
|
|
70
122
|
)
|
|
71
123
|
git('branch', '-u', most_main)
|
|
@@ -74,7 +126,8 @@ class SugarJar
|
|
|
74
126
|
# If this is a subfeature based on a local branch which has since
|
|
75
127
|
# been deleted, 'tracked branch' will automatically return <most_main>
|
|
76
128
|
# so we don't need any special handling for that
|
|
77
|
-
if !MAIN_BRANCHES.include?(curr) && base == "origin/#{curr}"
|
|
129
|
+
if !MAIN_BRANCHES.include?(curr) && base == "origin/#{curr}" &&
|
|
130
|
+
!skip_base_warning
|
|
78
131
|
SugarJar::Log.warn(
|
|
79
132
|
"This branch is tracking origin/#{curr}, which is probably your " +
|
|
80
133
|
'downstream (where you push _to_) as opposed to your upstream ' +
|