-
-
Notifications
You must be signed in to change notification settings - Fork 137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: better selection of base ref branch #532
Conversation
Shall we somehow combine github integrated branch name with this? It seems a bit inconsistant that we are using Edit To clarify, by combining, I meant to use the same mechanism to store the branch information. |
We're not setting the base in project settings, the base is calculated on every render of the view. The project settings are for getting a default override, which prompted me to suggest #531 , since I believe this should be in (global) default/user settings too. As for github integrated branch name, I'm not sure there's a better way to set settings persistently in sublime. |
The github integrate remote is also computed every time. My question is actually how we should store the default settings per project, using |
Anyway, this PR looks good to me. |
Ah.... The
|
I'm not sure there's a way to intelligently decide for the user which of the common branches he should rebase on. As far as I know Git doesn't store the original "branching-out" node. In other words, the user could have easily merged "master" into "feature" and it would detect that. If they do merge a sub-feature branch, deleting the merged branch will allow them to rebase on the next detected base. On the up side, it's still better than what we had (hardcoded "master"), and it can still be overridden in settings if they prefer to keep "base-ref" static. |
Another option, would be to find the oldest common ancestor, rather than the nearest, it would look something like: git branch --contains `diff -u <(git rev-list --first-parent subfeature) \
<(git rev-list --first-parent master) | sed -ne 's/^ //p' | head -1` --no-merged Update: Originally taken from here. But, it feels to me, that there's still no "right" answer for all scenarios. It's possible to ignore any merges and this may bring the user closer to true oldest ancestor node. |
@randy3k can you try the command in the above comment on your test case. If it would indicate it's better for you, maybe we can allow both options with a toggle? |
The above returns |
Sorry I copy-pasted hastily, I probably needed to update the branches like so: git branch --contains `diff -u <(git rev-list --first-parent feature) \
<(git rev-list --first-parent subfeature) | sed -ne 's/^ //p' | head -1` --no-merged
# or even
git branch --contains `git merge-base feature subfeature` --no-merged Here is how I understand this works, hope it helps: So, the current base detection (in this PR) works by getting all relative branches, then simply picks the first of those branches, and if it's different than self, returns it. However, with the above suggested solution, we would not stop at "nearest", rather we would then continue and compare current branch to the potential base branch, looking for the node these trees met (oldest common commit in rev-list). Then check if there is a branch that has this commit and is unmerged, and use that branch as base ref. |
The former one returns How about this? for s in $(git rev-list --first-parent feature); do
branches=`git branch --contains $s --no-merged`
[ -n "$branches" ] && echo $branches && break
done Note: The above may return more than one branch. |
The loops works nicely on my feature branches, but not at all on my master branch, for those it returns a single feature branch (unmerged!), which would be wrong as a base ref. Correct me if I'm wrong but, the only issue we currently have with the I think I'll add the filter (earliest common no-merge branches) suggested earlier, and see how that works for us. I still don't think there is a "one size fits all" solution, so we may also want to research a bit more of how others do it. |
Sounds a good idea. It would be also handy to preselect the corresponding branch in GsRebaseOnTopOfCommand. |
ff90635
to
c5e13d4
Compare
Ok ready for review guys! This was a bit troublesome and I want to avoid adding more functionality in this PR. And to be clear, this is by no means a perfect solution, there may still be cases where the detected base is clearly not what the users wants. Here's some stuff we need to address:
|
core/interfaces/rebase.py
Outdated
max_iterations = 100 | ||
for relative in relatives: | ||
util.debug.add_to_log('nearest_branch: Getting common commits with %s' % relative) | ||
relative_commits = self.git("rev-list", "--first-parent", relative).splitlines() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may set the max iterations by self.git("rev-list", "-{}".format(max_iterations), "--first-parent", relative)
core/interfaces/rebase.py
Outdated
|
||
if not base_ref: | ||
# use remote tracking branch as a sane default | ||
remote_branch = self._get_branch_status_components()[3] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.get_active_remote_branch().name_with_remote
may be better
core/interfaces/rebase.py
Outdated
@@ -284,6 +296,95 @@ def base_ref(self): | |||
|
|||
return base_ref | |||
|
|||
def nearest_branch(self, branch, default="master"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably worth breaking this out into a helper method, since there's some complexity here and it isn't strictly view-related.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean into it's own helper class? Initially I had this in BranchesMixin
since it's basically a branch detection mechanism, then I moved it here since felt it's very much related to rebase interface.
But, perhaps it's better if we move this into it's own mixin module: git_mixins.rebase
to potentially be used by other (rebase) commands. How do you feel about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, moving to git_mixins.rebase
sounds exactly right.
core/interfaces/rebase.py
Outdated
except GitSavvyError: | ||
return default | ||
|
||
util.debug.add_to_log('nearest_branch: found %s show-branch results' % |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Prefer "... {}".format(val)
over "... %s" % val
for consistency.
core/interfaces/rebase.py
Outdated
(len(relatives), relatives)) | ||
|
||
if not relatives: | ||
util.debug.add_to_log('No relatives found. Possibly on a root branch!') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this pattern of verbose debug logging!
core/interfaces/rebase.py
Outdated
util.debug.add_to_log('nearest_branch: Getting common commits with %s' % relative) | ||
relative_commits = self.git("rev-list", "--first-parent", relative).splitlines() | ||
common = None | ||
for i, l in enumerate(diff.compare(branch_commits, relative_commits)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer descriptive variable names here.
core/interfaces/rebase.py
Outdated
if branch == nearest: | ||
return default | ||
return nearest | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a huge function. Would be great if it could be decomposed somehow, but its okay if that would add extra complexity or damage readability.
Oh, and last comment. Would like to make sure this doesn't adversely effect performance too much. GitSavvy can already be slow, particularly on Windows machines without SSD. I'd like to avoid making it worse if possible. I have an older machine that I pull out for things like this, if you would like me to check this. |
@divmain It does add a tiny bit of overhead when opening rebase dashboard, but it's still surprisingly fast on my machine. I would appreciate if we could run this on an old machine! We'd also need a code base with branches that have diverged significantly, to test further. Other than that, I'd have to use this a bit more to test out more edge cases. |
It's already the default passed by the interface to the helper method. I.e |
@asfaltboy, let me know when all review comments have been addressed and I'll pull this down onto an old laptop :) |
@randy3k still looking at this? |
Hmmm, I could work on this if @asfaltboy agrees. |
Ops, i though I saw you opening this. |
I keep postponing my duties, too much work during the week and weekends are busy. I'll try to address the concerns when able |
No problem. Everyone has a real life. |
c5e13d4
to
2fcec66
Compare
core/interfaces/rebase.py
Outdated
|
||
if not base_ref: | ||
# use remote tracking branch as a sane default | ||
remote_branch = self.get_active_remote_branch().name_with_remote |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.get_active_remote_branch()
could be None
if there is no remote tracking, you probably only need get_upstream_for_active_branch()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have fixed it.
It would be more convenient if |
project_data = sublime.active_window().project_data() or {} | ||
project_settings = project_data.get('settings', {}) | ||
base_ref = project_settings.get("rebase_default_base_ref", "master") | ||
base_ref = project_settings.get("rebase_default_base_ref") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is irrelevant to this PR, but I would also like to discuss about this setting rebase_default_base_ref
. It seems it is the only place we use a project setting while we are using git config
to set GitHub integration repo and branch.
I just want to make sure there will be a consistent way for storing user settings. So which one shall we use if there is such a need? A: git config
or B: sublime text project settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally don't think "sublime-specific settings" belong in git config
...
My project-settings files are synced to dropbox, and I'd like to avoid defining the override ref again for every new project when I switch machines. Then again, this feature kind of makes this a non-issue.
However, now that you mentioned the Github settings, maybe it could be good to store them in project settings too, unless it makes sense to use these outside of GitSavvy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sublime-project
file might be shared within a team (though it is no likely), the git config
is not. It might be one of the reasons why historically we were using git config
rather than project settings.
Though, I agree that those information should not be stored at git config
neither.
Before, we used a base ref that was specified in settings, otherwise defaulting to hardcoded "master". Now, we find the nearest common ancestor branch (after some filtering) and use that as our default base, still allowing users to override it in settings.
- use .get_active_remote_branch().name_with_remote for clarity - be consistant with string formatting in log calls - limit to max revision count in `git rev-list` call - rename max_iterations to max_revisions for clarity
This avoids wrongly using commit message parts that include strings in square brackets as branches. For example before this fix, the following commit would return WIP as branch name: `[develo] [WIP] Fix: some bug`
- As suggested by @divmain the method was moved into a separate mixin for reuse, as well as, splitted into smaller parts - Use `git branch --merged` instead of `--no-merged. This fixes a bug where the most obvious branches are not returned since no-merged means that only branches that are "unreachable" from given node are returned, and we actually want the opposite.
ba3553c
to
ff8bcb1
Compare
Before, we used a base ref that was specified in settings, otherwise defaulting to hardcoded "master".
Now, we find the nearest common ancestor branch and use that as our default base, still allowing override in settings.
Fixes #499
The maintainers of this repo require that all pull request submitters agree and adhere to the following:
(required)
The maintainers of this repository require you to select the semantic version type that
the changes in this pull request represent. Please select one of the following: