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.
@@ -1,9 +1,18 @@
1
1
  class SugarJar
2
2
  class Commands
3
- def bclean(name = nil)
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 bcleanall
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
- if MAIN_BRANCHES.include?(branch)
22
- SugarJar::Log.debug("Skipping #{branch}")
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
- def clean_branch(name)
48
- die("Cannot remove #{name} branch") if MAIN_BRANCHES.include?(name)
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
- return false unless safe_to_clean(name)
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
- checkout_main_branch
55
- git('branch', '-D', name)
56
- rebase
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', tracked_branch, branch
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, tracked_branch)
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
- git('reset', '--hard', tracked_branch)
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
- def run_check(type)
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
- $stdout.print(
106
- "\nWould you like to\n\t[q]uit and inspect\n\t[a]mend the " +
107
- "changes to the current commit and re-run\n > ",
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?
@@ -4,7 +4,7 @@ class SugarJar
4
4
  class Commands
5
5
  def debuginfo(*args)
6
6
  puts "sugarjar version #{SugarJar::VERSION}"
7
- puts ghcli('version').stdout
7
+ puts forge('version').stdout
8
8
  puts git('version').stdout
9
9
 
10
10
  puts "Config: #{JSON.pretty_generate(args[0])}"
@@ -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
- fbase = fprefix(base)
10
- base = fbase if all_local_branches.include?(fbase)
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
- 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
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 = File.basename(repo, '.git')
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 == @ghuser
16
+ if org == @forge_user
17
17
  git('clone', canonicalize_repo(repo), dir, *)
18
18
  else
19
- ghcli('repo', 'fork', '--clone', canonicalize_repo(repo), dir, *)
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
- 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)
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
- return if origin_branch == upstream_branch
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 " +
@@ -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 = tracked_branch(fallback: false)
116
+ base ||= tracked_branch(fallback: false)
65
117
  # rubocop:enable Style/HashSyntax
66
118
  unless base
67
119
  SugarJar::Log.info(
68
- 'The brach we were tracking is gone, resetting tracking to ' +
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 ' +