├── .python-version ├── CONTRIBUTING.md ├── Context.sublime-menu ├── Default (Linux).sublime-keymap ├── Default (OSX).sublime-keymap ├── Default (Windows).sublime-keymap ├── Default.sublime-commands ├── Default.sublime-keymap ├── GitSavvy.sublime-project ├── GitSavvy.sublime-settings ├── GitSavvy.tmPreferences ├── LICENSE ├── Main.sublime-menu ├── README.md ├── common ├── __init__.py ├── commands │ ├── __init__.py │ ├── debug.py │ ├── help.py │ └── view_manipulation.py ├── global_events.py ├── interwebs.py ├── theme_generator.py ├── ui.py └── util │ ├── __init__.py │ ├── actions.py │ ├── dates.py │ ├── debug.py │ ├── diff_string.py │ ├── file.py │ ├── log.py │ ├── parse_diff.py │ ├── reload.py │ └── view.py ├── core ├── __init__.py ├── base_commands.py ├── commands │ ├── __init__.py │ ├── amend.py │ ├── blame.py │ ├── branch.py │ ├── changelog.py │ ├── checkout.py │ ├── cherry_pick.py │ ├── commit.py │ ├── commit_compare.py │ ├── config.py │ ├── context_menu.py │ ├── custom.py │ ├── diff.py │ ├── fetch.py │ ├── fixup.py │ ├── flow.py │ ├── help_panel.py │ ├── ignore.py │ ├── init.py │ ├── inline_diff.py │ ├── intra_line_colorizer.py │ ├── line_history.py │ ├── log.py │ ├── log_graph.py │ ├── log_graph_colorizer.py │ ├── log_graph_rebase_actions.py │ ├── log_graph_smart_copy.py │ ├── merge.py │ ├── mv.py │ ├── navigate.py │ ├── next_hunk.py │ ├── pull.py │ ├── push.py │ ├── quick_commit.py │ ├── quick_stage.py │ ├── reflog.py │ ├── remote.py │ ├── reset.py │ ├── revert.py │ ├── show_commit.py │ ├── show_commit_info.py │ ├── show_file_at_commit.py │ ├── stage_diff.py │ ├── stage_hunk.py │ ├── stash.py │ ├── status_bar.py │ └── tag.py ├── exceptions.py ├── fns.py ├── git_command.py ├── git_mixins │ ├── __init__.py │ ├── active_branch.py │ ├── branches.py │ ├── checkout_discard.py │ ├── history.py │ ├── ignore.py │ ├── merge.py │ ├── rebase.py │ ├── remotes.py │ ├── rewrite.py │ ├── stage_unstage.py │ ├── stash.py │ ├── status.py │ └── tags.py ├── interfaces │ ├── __init__.py │ ├── branch.py │ ├── rebase.py │ ├── status.py │ └── tags.py ├── parse_diff.py ├── runtime.py ├── settings.py ├── store.py ├── types.py ├── ui_mixins │ ├── __init__.py │ ├── input_panel.py │ └── quick_panel.py ├── utils.py └── view.py ├── dependencies.json ├── docs ├── README.md ├── branch_mgmt.md ├── commit.md ├── custom.md ├── debug.md ├── flow.md ├── github.md ├── gitlab.md ├── history.md ├── ignoring.md ├── misc.md ├── rebase.md ├── remotes.md ├── staging.md ├── stash.md ├── status.md ├── tag_mgmt.md └── testing.md ├── git_savvy.py ├── github ├── __init__.py ├── commands │ ├── __init__.py │ ├── add_fork_as_remote.py │ ├── commit.py │ ├── configure.py │ ├── create_fork.py │ ├── create_repo.py │ ├── open_issue.py │ ├── open_on_remote.py │ └── pull_request.py ├── git_mixins │ ├── __init__.py │ └── remotes.py └── github.py ├── gitlab ├── __init__.py ├── commands │ ├── __init__.py │ ├── configure.py │ ├── merge_request.py │ └── open_on_remote.py ├── git_mixins │ ├── __init__.py │ └── remotes.py └── gitlab.py ├── messages.json ├── messages ├── 1.1.0.txt ├── 1.2.0.txt ├── 1.3.1.txt ├── 1.4.0.txt ├── 2.0.0.txt ├── 2.1.0.txt ├── 2.10.0.txt ├── 2.11.0.txt ├── 2.12.0.txt ├── 2.12.1.txt ├── 2.13.0.txt ├── 2.14.0.txt ├── 2.14.1.txt ├── 2.14.2.txt ├── 2.16.2.txt ├── 2.16.4.txt ├── 2.16.5.txt ├── 2.16.6.txt ├── 2.16.7.txt ├── 2.17.0.txt ├── 2.17.1.txt ├── 2.17.3.txt ├── 2.17.4.txt ├── 2.18.0.txt ├── 2.19.0.txt ├── 2.2.0.txt ├── 2.20.0.txt ├── 2.21.0.txt ├── 2.22.0.txt ├── 2.23.0.txt ├── 2.25.0.txt ├── 2.26.0.txt ├── 2.27.0.txt ├── 2.28.0.txt ├── 2.3.0.txt ├── 2.30.0.txt ├── 2.31.0.txt ├── 2.34.0.txt ├── 2.36.0.txt ├── 2.38.0.txt ├── 2.39.0.txt ├── 2.4.0.txt ├── 2.41.0.txt ├── 2.42.0.txt ├── 2.5.0.txt ├── 2.6.0.txt ├── 2.7.0.txt ├── 2.8.0.txt ├── 2.9.0.txt └── install.txt ├── pyproject.toml ├── requirements-dev.lock ├── requirements.lock └── syntax ├── blame.sublime-settings ├── blame.sublime-syntax ├── branch.sublime-settings ├── branch.sublime-syntax ├── dashboard.sublime-syntax ├── diff.sublime-settings ├── diff.sublime-syntax ├── diff_view.sublime-settings ├── diff_view.sublime-syntax ├── graph.sublime-settings ├── graph.sublime-syntax ├── help.sublime-settings ├── help.sublime-syntax ├── make_commit.sublime-settings ├── make_commit.sublime-syntax ├── output_panel.sublime-settings ├── output_panel.sublime-syntax ├── rebase.sublime-settings ├── rebase.sublime-syntax ├── show_commit.sublime-settings ├── show_commit.sublime-syntax ├── show_commit_diffstat.sublime-syntax ├── status.sublime-settings ├── status.sublime-syntax ├── tags.sublime-settings ├── tags.sublime-syntax └── test ├── syntax_test_blame.txt ├── syntax_test_branch.txt ├── syntax_test_dashboard.txt ├── syntax_test_diff.txt ├── syntax_test_graph.txt ├── syntax_test_graph_pick_axe.txt ├── syntax_test_make_commit.txt ├── syntax_test_output_panel.txt ├── syntax_test_rebase.txt ├── syntax_test_show_commit.txt ├── syntax_test_show_commit_with_stat.txt ├── syntax_test_status.txt └── syntax_test_tags.txt /.python-version: -------------------------------------------------------------------------------- 1 | 3.8 -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute 2 | 3 | If you're interested in adding features, reporting/fixing bugs, or just discussing the future of GitSavvy, this is the place to start. Please feel free to reach out if you have questions or comments by opening an [issue](https://github.com/timbrel/GitSavvy/issues). 4 | 5 | 6 | ## Adding new features 7 | 8 | If there's a feature you'd like to see that isn't already in-progress or documented, there are two ways you can go about it: 9 | 10 | - open an issue for discussion; or 11 | - fork and submit a PR. 12 | 13 | Either way is fine, so long as you remember your PR may not be accepted as-is or at all. If a feature request is reasonable, though, it'll most likely be included one way or another. 14 | 15 | Some other things to remember: 16 | 17 | - Please respect the project layout and hierarchy, to keep things organized. 18 | - All Python code should be PEP8 compliant with few exceptions (e.g. imports in `__init__.py` files). 19 | - Include docstrings for all classes and functions, unless functionality is obvious at a glance. 20 | - Include descriptions for issues and PRs. 21 | 22 | 23 | ## Commit message structure 24 | 25 | Please follow the following commit message structure when submitting your pull request to GitSavvy: 26 | 27 | TYPE: Short commit message 28 | 29 | Detailed 30 | commit 31 | info 32 | 33 | For the value of **`TYPE`**, please use one of **`Feature`**, **`Enhancement`**, or **`Fix`**. 34 | 35 | This is done in order to help us automate tasks such as changelog generation. 36 | 37 | 38 | # Bugs 39 | 40 | If you encounter a bug, please check for an open issue that already captures the problem you've run into. If it doesn't exist yet, create it! 41 | 42 | Please include the following information when filing a bug: 43 | 44 | - Sublime Text version number 45 | - Git version number 46 | - OS type and version 47 | - Console error output 48 | - A description of the problem. 49 | - Steps to reproduce, if possible 50 | - If the bug is caused by a failing command, [include a debug log](docs/debug.md#providing-a-debug-log). 51 | 52 | 53 | # Documentation 54 | 55 | If you make changes, please remember to update the user documentation to reflect the new behavior. 56 | 57 | 58 | ## Package Testing 59 | 60 | Check the implementation details of the [tests](docs/testing.md). 61 | 62 | # Publishing 63 | 64 | Hotfixes should be submitted to `master` branch. As part of the PR process, 65 | you will be asked to provide the information necessary to make that happen. 66 | 67 | Pull requests on new features should be submitted to `dev` branch and will be 68 | regularly merged into `master` after evaluations over an extended period of 69 | time. 70 | 71 | # Issue Triage [![Open Source Helpers](https://www.codetriage.com/divmain/gitsavvy/badges/users.svg)](https://www.codetriage.com/divmain/gitsavvy) 72 | 73 | You can triage issues which may include reproducing bug reports or asking for vital information, such as version numbers or reproduction instructions. If you would like to start triaging issues, one easy way to get started is to [subscribe to gitsavvy on CodeTriage](https://www.codetriage.com/divmain/gitsavvy). 74 | -------------------------------------------------------------------------------- /Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "GitSavvy: Line History", 4 | "command": "gs_ctx_line_history" 5 | }, 6 | { 7 | "caption": "GitSavvy: Pick-axe", 8 | "command": "gs_ctx_pick_axe" 9 | }, 10 | { 11 | "caption": "GitSavvy: Stage selected hunk", 12 | "command": "gs_ctx_stage_hunk" 13 | } 14 | // { 15 | // "caption": " ...", 16 | // "children": [ 17 | // { 18 | // "caption": "Repo History", 19 | // "command": "gs_graph", 20 | // "args": { "all": true } 21 | // }, 22 | // { 23 | // "caption": "Path History", 24 | // "command": "gs_graph_current_path" 25 | // }, 26 | // { 27 | // "caption": "File History", 28 | // "command": "gs_graph_current_file", 29 | // "args": { "all": false } 30 | // }, 31 | // { "caption": "-" }, 32 | // { 33 | // "caption": "Show current file revision at HEAD", 34 | // "command": "gs_show_file_at_commit" 35 | // }, 36 | // ] 37 | // }, 38 | ] 39 | -------------------------------------------------------------------------------- /GitSavvy.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "build_systems": 3 | [ 4 | { 5 | "name": "Reload GitSavvy", 6 | "target": "gs_reload_modules_debug", 7 | }, 8 | { 9 | "name": "Test GitSavvy", 10 | "target": "unit_testing_current_package", 11 | } 12 | ], 13 | "folders": 14 | [ 15 | { 16 | "folder_exclude_patterns": 17 | [ 18 | ".git", 19 | "__pycache__" 20 | ], 21 | "path": ".", 22 | } 23 | ], 24 | "settings": 25 | { 26 | "GitSavvy": 27 | { 28 | }, 29 | "SublimeLinter.linters.mypy.excludes": ["*/tests/*"], 30 | "rulers": 31 | [ 32 | 100, 33 | 120 34 | ], 35 | "tab_size": 4, 36 | "translate_tabs_to_spaces": true, 37 | "SublimeLinter.linters.ruff.disable": true, 38 | "SublimeLinter.linters.ruff-lsp.disable": true, 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /GitSavvy.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | Symbol List 7 | scope 8 | gitsavvy.gotosymbol 9 | settings 10 | 11 | showInSymbolList 12 | 1 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dale Bustad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "preferences", 4 | "children": [ 5 | { 6 | "caption": "Package Settings", 7 | "mnemonic": "P", 8 | "id": "package-settings", 9 | "children": [ 10 | { 11 | "caption": "GitSavvy", 12 | "children": [ 13 | { 14 | "caption": "Settings", 15 | "command": "edit_settings", 16 | "args": { 17 | "base_file": "${packages}/GitSavvy/GitSavvy.sublime-settings", 18 | "default": "// Settings in here override those in \"GitSavvy/GitSavvy.sublime-settings\",\n\n{\n\t$0\n}\n" 19 | } 20 | }, { 21 | "caption": "Key Bindings", 22 | "command": "edit_settings", 23 | "args": { 24 | "base_file": "${packages}/GitSavvy/Default (${platform}).sublime-keymap" 25 | } 26 | } 27 | ] 28 | } 29 | ] 30 | } 31 | ] 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timbrel/GitSavvy/f0c9067eb061498ba7a07960e5cbaefb414b4994/common/__init__.py -------------------------------------------------------------------------------- /common/commands/__init__.py: -------------------------------------------------------------------------------- 1 | from .debug import * 2 | from .view_manipulation import * 3 | from .help import * 4 | -------------------------------------------------------------------------------- /common/commands/debug.py: -------------------------------------------------------------------------------- 1 | """ 2 | Sublime commands related to development and debugging. 3 | """ 4 | 5 | from sublime_plugin import WindowCommand 6 | 7 | from ..util import debug, reload 8 | from ...core.settings import GitSavvySettings 9 | from ...core.view import replace_view_content 10 | 11 | 12 | __all__ = ( 13 | "gs_reload_modules_debug", 14 | "gs_start_logging", 15 | "gs_stop_logging", 16 | "gs_view_git_log", 17 | ) 18 | 19 | 20 | class gs_reload_modules_debug(WindowCommand): 21 | 22 | """ 23 | When triggered, reload all GitSavvy submodules. 24 | """ 25 | 26 | def run(self): 27 | reload.reload_plugin() 28 | 29 | def is_visible(self): 30 | return GitSavvySettings().get("dev_mode") 31 | 32 | 33 | class gs_start_logging(WindowCommand): 34 | 35 | """ 36 | Starts recording all interactions with Git for reporting issues. 37 | """ 38 | 39 | def run(self): 40 | debug.start_logging() 41 | 42 | 43 | class gs_stop_logging(WindowCommand): 44 | 45 | """ 46 | Stops recording interactions with Git. 47 | """ 48 | 49 | def run(self): 50 | debug.stop_logging() 51 | 52 | 53 | class gs_view_git_log(WindowCommand): 54 | 55 | """ 56 | Displays the recent recording. 57 | """ 58 | 59 | def run(self): 60 | log = debug.get_log() 61 | view = self.window.new_file() 62 | view.set_scratch(True) 63 | view.settings().set("syntax", "Packages/JavaScript/JSON.sublime-syntax") 64 | replace_view_content(view, log) 65 | -------------------------------------------------------------------------------- /common/commands/view_manipulation.py: -------------------------------------------------------------------------------- 1 | from sublime_plugin import TextCommand 2 | from ...core.git_command import GitCommand 3 | 4 | 5 | __all__ = ( 6 | "gs_handle_vintageous", 7 | "gs_handle_arrow_keys" 8 | ) 9 | 10 | 11 | class gs_handle_vintageous(TextCommand, GitCommand): 12 | 13 | """ 14 | Set the vintageous_friendly view setting if needed. 15 | Enter insert mode if vintageous_enter_insert_mode option is enabled. 16 | """ 17 | 18 | def run(self, edit): 19 | if self.savvy_settings.get("vintageous_friendly"): 20 | self.view.settings().set("git_savvy.vintageous_friendly", True) 21 | if self.savvy_settings.get("vintageous_enter_insert_mode"): 22 | self.view.settings().set("vintageous_reset_mode_when_switching_tabs", False) 23 | # NeoVintageous renamed the command starting with v1.22.0. 24 | # We call both commands for backwards compatibility. 25 | self.view.run_command("_enter_insert_mode") 26 | self.view.run_command("nv_enter_insert_mode") # since NeoVintageous 1.22.0 27 | 28 | 29 | class gs_handle_arrow_keys(TextCommand, GitCommand): 30 | 31 | """ 32 | Set the arrow_keys_navigation view setting if needed. 33 | It allows navigation by using arrow keys. 34 | """ 35 | 36 | def run(self, edit): 37 | if self.savvy_settings.get("arrow_keys_navigation"): 38 | self.view.settings().set("git_savvy.arrow_keys_navigation", True) 39 | -------------------------------------------------------------------------------- /common/util/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from .parse_diff import parse_diff 4 | from . import dates 5 | from . import view 6 | from . import file 7 | from . import log 8 | from . import actions 9 | from . import debug 10 | from . import diff_string 11 | from . import reload 12 | 13 | super_key = "super" if sys.platform == "darwin" else "ctrl" 14 | -------------------------------------------------------------------------------- /common/util/actions.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | import sublime 4 | 5 | from ...core.settings import GitSavvySettings 6 | 7 | from typing import Callable, Optional, TypeVar, TYPE_CHECKING 8 | T = TypeVar('T') 9 | 10 | if TYPE_CHECKING: 11 | from typing_extensions import ParamSpec 12 | P = ParamSpec('P') 13 | 14 | 15 | def destructive(description: str) -> "Callable[[Callable[P, T]], Callable[P, Optional[T]]]": 16 | def decorator(fn: "Callable[P, T]") -> "Callable[P, Optional[T]]": 17 | @wraps(fn) 18 | def wrapped_fn(*args: "P.args", **kwargs: "P.kwargs") -> Optional[T]: 19 | if GitSavvySettings().get("prompt_before_destructive_action"): 20 | message = ( 21 | "You are about to {desc}. " 22 | "This is a destructive action. \n\n" 23 | "Are you SURE you want to do this? \n\n" 24 | "(you can disable this prompt in " 25 | "GitSavvy settings)").format(desc=description) 26 | if not sublime.ok_cancel_dialog(message): 27 | return None 28 | 29 | return fn(*args, **kwargs) 30 | return wrapped_fn 31 | return decorator 32 | -------------------------------------------------------------------------------- /common/util/dates.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | TEN_MINS = 600 4 | ONE_HOUR = 3600 5 | TWO_HOURS = 7200 6 | ONE_DAY = 86400 7 | 8 | 9 | def fuzzy(event, base=None, date_format=None): 10 | if not base: 11 | base = datetime.now() 12 | 13 | if date_format: 14 | event = datetime.strptime(event, date_format) 15 | elif type(event) == str: 16 | event = datetime.fromtimestamp(int(event)) 17 | elif type(event) == int: 18 | event = datetime.fromtimestamp(event) 19 | elif type(event) != datetime: 20 | raise Exception( 21 | "Cannot convert object `{}` to fuzzy date string".format(event)) 22 | 23 | delta = base - event 24 | 25 | if delta.days == 0: 26 | if delta.seconds < 60: 27 | return "{} seconds ago".format(delta.seconds) 28 | 29 | elif delta.seconds < 120: 30 | return "1 min and {} secs ago".format(delta.seconds - 60) 31 | 32 | elif delta.seconds < TEN_MINS: 33 | return "{} mins and {} secs ago".format( 34 | delta.seconds // 60, 35 | delta.seconds % 60) 36 | 37 | elif delta.seconds < ONE_HOUR: 38 | return "{} minutes ago".format(delta.seconds // 60) 39 | 40 | elif delta.seconds < TWO_HOURS: 41 | return "1 hour and {} mins ago".format( 42 | delta.seconds % ONE_HOUR // 60) 43 | 44 | return "over {} hours ago".format(delta.seconds // ONE_HOUR) 45 | 46 | elif delta.days < 2: 47 | return "over a day ago" 48 | 49 | elif delta.days < 7: 50 | return "over {} days ago".format(delta.days) 51 | 52 | return "{date:%b} {date.day}, {date.year}".format(date=event) 53 | -------------------------------------------------------------------------------- /common/util/diff_string.py: -------------------------------------------------------------------------------- 1 | import re 2 | from difflib import SequenceMatcher 3 | from collections import namedtuple 4 | 5 | Change = namedtuple("Change", ( 6 | "type", 7 | "old_start", 8 | "old_end", 9 | "new_start", 10 | "new_end" 11 | )) 12 | REPLACE = "replace" 13 | DELETE = "delete" 14 | INSERT = "insert" 15 | 16 | 17 | boundary = re.compile(r"(\W)") 18 | 19 | 20 | def get_indices(chunks): 21 | idx = 0 22 | indices = [] 23 | for chunk in chunks: 24 | indices.append(idx) 25 | idx += len(chunk) 26 | indices.append(idx) 27 | return indices 28 | 29 | 30 | def get_changes(old, new): 31 | # if one of the inputs, either old or new is more then 10 000 characters 32 | # we skip trying to find the words which changed. If a hunk is more than 33 | # 10 000 characters it is most likely a generated change. 34 | # We skip since this calculation take a lot of time then it gets bigger. 35 | if max(len(old), len(new)) > 10000: 36 | return [] 37 | 38 | old_chunks = tuple(filter(lambda x: x, boundary.split(old))) 39 | new_chunks = tuple(filter(lambda x: x, boundary.split(new))) 40 | old_indices = get_indices(old_chunks) 41 | new_indices = get_indices(new_chunks) 42 | 43 | matcher = SequenceMatcher(a=old_chunks, b=new_chunks, autojunk=False) 44 | 45 | if matcher.quick_ratio() < 0.75 or matcher.ratio() < 0.75: 46 | return [] 47 | 48 | return [Change(change_type, old_indices[os], old_indices[oe], new_indices[ns], new_indices[ne]) 49 | for change_type, os, oe, ns, ne in matcher.get_opcodes() 50 | if not change_type == "equal"] 51 | -------------------------------------------------------------------------------- /common/util/file.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | import os 3 | 4 | import sublime 5 | 6 | from GitSavvy.core.fns import maybe 7 | from typing import Dict 8 | 9 | 10 | syntax_file_map = {} # type: Dict[str, str] 11 | 12 | 13 | def guess_syntax_for_file(window, filename): 14 | # type: (sublime.Window, str) -> str 15 | view = window.find_open_file(filename) 16 | if view: 17 | syntax = view.settings().get("syntax") 18 | remember_syntax_choice(filename, syntax) 19 | return syntax 20 | return get_syntax_for_file(filename) 21 | 22 | 23 | def remember_syntax_choice(filename, syntax): 24 | # type: (str, str) -> None 25 | syntax_file_map[get_file_extension(filename) or filename] = syntax 26 | 27 | 28 | def get_syntax_for_file(filename, default="Packages/Text/Plain text.tmLanguage"): 29 | # type: (str, str) -> str 30 | return ( 31 | syntax_file_map.get(filename) 32 | or syntax_file_map.get(get_file_extension(filename)) 33 | or maybe(lambda: sublime.find_syntax_for_file(filename).path) # type: ignore[union-attr] 34 | or default 35 | ) 36 | 37 | 38 | def get_file_extension(filename): 39 | # type: (str) -> str 40 | return os.path.splitext(filename)[1][1:] 41 | 42 | 43 | def get_file_contents_binary(repo_path, file_path): 44 | """ 45 | Given an absolute file path, return the binary contents of that file 46 | as a string. 47 | """ 48 | file_path = os.path.join(repo_path, file_path) 49 | with safe_open(file_path, "rb") as f: 50 | binary = f.read() 51 | binary = binary.replace(b"\r\n", b"\n") 52 | binary = binary.replace(b"\r", b"") 53 | return binary 54 | 55 | 56 | def get_file_contents(repo_path, file_path): 57 | """ 58 | Given an absolute file path, return the text contents of that file 59 | as a string. 60 | """ 61 | binary = get_file_contents_binary(repo_path, file_path) 62 | try: 63 | return binary.decode('utf-8') 64 | except UnicodeDecodeError: 65 | return binary.decode('latin-1') 66 | 67 | 68 | @contextmanager 69 | def safe_open(filename, mode, *args, **kwargs): 70 | try: 71 | with open(filename, mode, *args, **kwargs) as file: 72 | yield file 73 | except PermissionError as e: 74 | sublime.ok_cancel_dialog("GitSavvy could not access file: \n{}".format(e)) 75 | raise e 76 | except OSError as e: 77 | sublime.ok_cancel_dialog("GitSavvy encountered an OS error: \n{}".format(e)) 78 | raise e 79 | -------------------------------------------------------------------------------- /common/util/log.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sublime 3 | 4 | from GitSavvy.core.view import replace_view_content 5 | 6 | from typing import Optional 7 | 8 | 9 | PANEL_NAME = "GitSavvy" 10 | ANSI_ESCAPE_RE = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]') 11 | 12 | 13 | def normalize(string): 14 | # type: (str) -> str 15 | return ANSI_ESCAPE_RE.sub('', string.replace('\r\n', '\n').replace('\r', '\n')) 16 | 17 | 18 | def init_panel(window): 19 | # type: (sublime.Window) -> sublime.View 20 | panel_view = create_panel(window) 21 | show_panel(window) 22 | return panel_view 23 | 24 | 25 | def display_panel(window, message): 26 | # type: (sublime.Window, str) -> sublime.View 27 | panel_view = init_panel(window) 28 | append_to_panel(panel_view, message) 29 | panel_view.show(0) 30 | return panel_view 31 | 32 | 33 | def ensure_panel(window): 34 | # type: (sublime.Window) -> sublime.View 35 | return get_panel(window) or create_panel(window) 36 | 37 | 38 | def get_panel(window): 39 | # type: (sublime.Window) -> Optional[sublime.View] 40 | return window.find_output_panel(PANEL_NAME) 41 | 42 | 43 | def create_panel(window): 44 | # type: (sublime.Window) -> sublime.View 45 | panel_view = window.create_output_panel(PANEL_NAME) 46 | panel_view.set_syntax_file("Packages/GitSavvy/syntax/output_panel.sublime-syntax") 47 | return panel_view 48 | 49 | 50 | def show_panel(window): 51 | # type: (sublime.Window) -> None 52 | window.run_command("show_panel", {"panel": "output.{}".format(PANEL_NAME)}) 53 | 54 | 55 | def append_to_panel(panel, message): 56 | # type: (sublime.View, str) -> None 57 | # We support standard progress bars with "\r" line endings! 58 | # If we see such a line ending, we remember where we started 59 | # our write as `virtual_cursor` as this is where the next 60 | # write must begin. 61 | has_trailing_carriage_return = message.endswith("\r") 62 | message = normalize(message) 63 | 64 | eof = panel.size() 65 | cursor = panel.settings().get("virtual_cursor") or eof 66 | region = sublime.Region(cursor, eof) 67 | replace_view_content(panel, message, region=region) 68 | panel.settings().set("virtual_cursor", cursor if has_trailing_carriage_return else None) 69 | 70 | eof_after_append = panel.size() 71 | panel.show(eof_after_append) 72 | -------------------------------------------------------------------------------- /common/util/parse_diff.py: -------------------------------------------------------------------------------- 1 | """ 2 | Parse and process output from `git diff` command. 3 | """ 4 | 5 | from GitSavvy.core.parse_diff import HunkLine, SplittedDiff 6 | 7 | from typing import Iterator, List, NamedTuple 8 | from GitSavvy.core.types import LineNo 9 | 10 | 11 | class Change(NamedTuple): 12 | raw: str 13 | type: str 14 | head_pos: LineNo 15 | saved_pos: LineNo 16 | text: str 17 | 18 | 19 | class Hunk(NamedTuple): 20 | raw_lines: List[str] 21 | changes: List[Change] 22 | head_start: LineNo 23 | head_length: int 24 | saved_start: LineNo 25 | saved_length: int 26 | 27 | 28 | def parse_diff(diff_str): 29 | # type: (str) -> List[Hunk] 30 | """ 31 | Given the string output from a `git diff` command, parse the string into 32 | hunks and, more granularly, meta-data and change information for each of 33 | those hunks. 34 | """ 35 | hunks = [] 36 | for hunk in SplittedDiff.from_string(diff_str).hunks: 37 | head_start, head_length, saved_start, saved_length = hunk.header().parse() 38 | changes = _get_changes(hunk.content().lines(), head_start, saved_start) 39 | # Remove lines warning about "No newline at end of file"; change.type will == `\`. 40 | changes_filtered = [change for change in changes if change.type != "\\"] 41 | hunks.append( 42 | Hunk( 43 | hunk.text.splitlines(keepends=True), 44 | changes_filtered, 45 | head_start, 46 | head_length, 47 | saved_start, 48 | saved_length 49 | ) 50 | ) 51 | 52 | return hunks 53 | 54 | 55 | def _get_changes(hunk_lines, head_start, saved_start): 56 | # type: (List[HunkLine], LineNo, LineNo) -> Iterator[Change] 57 | """ 58 | Transform a list of `+` or `-` lines from a `git diff` output 59 | into tuples with the original raw line, the type of the change, 60 | the position of the HEAD- and saved- versions at that line, and 61 | the text of the line with the `+` or `-` removed. 62 | """ 63 | head_pos = head_start 64 | saved_pos = saved_start 65 | 66 | for line in hunk_lines: 67 | yield Change(line.text, line.mode, head_pos, saved_pos, line.content) 68 | if line.is_from_line(): 69 | head_pos += 1 70 | elif line.is_to_line(): 71 | saved_pos += 1 72 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timbrel/GitSavvy/f0c9067eb061498ba7a07960e5cbaefb414b4994/core/__init__.py -------------------------------------------------------------------------------- /core/commands/__init__.py: -------------------------------------------------------------------------------- 1 | from .amend import * 2 | from .blame import * 3 | from .branch import * 4 | from .changelog import * 5 | from .checkout import * 6 | from .cherry_pick import * 7 | from .commit import * 8 | from .commit_compare import * 9 | from .config import * 10 | from .context_menu import * 11 | from .custom import * 12 | from .diff import * 13 | from .fetch import * 14 | from .fixup import * 15 | from .flow import * 16 | from .help_panel import * 17 | from .ignore import * 18 | from .init import * 19 | from .inline_diff import * 20 | from .line_history import * 21 | from .log import * 22 | from .log_graph import * 23 | from .log_graph_rebase_actions import * 24 | from .log_graph_smart_copy import * 25 | from .merge import * 26 | from .mv import * 27 | from .navigate import * 28 | from .next_hunk import * 29 | from .pull import * 30 | from .push import * 31 | from .quick_commit import * 32 | from .quick_stage import * 33 | from .reflog import * 34 | from .remote import * 35 | from .reset import * 36 | from .revert import * 37 | from .show_commit import * 38 | from .show_commit_info import * 39 | from .show_file_at_commit import * 40 | from .stage_diff import * 41 | from .stage_hunk import * 42 | from .stash import * 43 | from .status_bar import * 44 | from .tag import * 45 | -------------------------------------------------------------------------------- /core/commands/amend.py: -------------------------------------------------------------------------------- 1 | from sublime_plugin import WindowCommand 2 | 3 | from ..git_command import GitCommand 4 | 5 | 6 | __all__ = ( 7 | "gs_amend", 8 | "gs_quick_stage_current_file_and_amend", 9 | ) 10 | 11 | 12 | class gs_amend(WindowCommand, GitCommand): 13 | def run(self): 14 | self.window.run_command("gs_commit", {"amend": True}) 15 | 16 | 17 | class gs_quick_stage_current_file_and_amend(gs_amend, GitCommand): 18 | def run(self): 19 | file_path = self.file_path 20 | if not file_path: 21 | self.window.status_message("Cannot staged unnamed buffer.") 22 | return 23 | 24 | self.git("add", "--", file_path) 25 | self.window.status_message("staged {}".format(self.get_rel_path(file_path))) 26 | super().run() 27 | -------------------------------------------------------------------------------- /core/commands/cherry_pick.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | from .log import gs_log_by_branch 4 | from ...common import util 5 | from GitSavvy.core.base_commands import GsWindowCommand 6 | from GitSavvy.core.runtime import on_worker 7 | 8 | 9 | __all__ = ( 10 | "gs_cherry_pick", 11 | "gs_cherry_pick_abort", 12 | "gs_cherry_pick_continue", 13 | "gs_cherry_pick_skip", 14 | ) 15 | 16 | 17 | class gs_cherry_pick(gs_log_by_branch): 18 | 19 | def log(self, **kwargs): # type: ignore[override] 20 | kwargs["cherry"] = True 21 | kwargs["start_end"] = ("", kwargs["branch"]) 22 | return super().log(**kwargs) 23 | 24 | def do_action(self, commit_hash, **kwargs): 25 | self.git("cherry-pick", commit_hash) 26 | sublime.active_window().status_message("Commit %s cherry-picked successfully." % commit_hash) 27 | util.view.refresh_gitsavvy(self.window.active_view()) 28 | 29 | 30 | class gs_cherry_pick_abort(GsWindowCommand): 31 | @on_worker 32 | def run(self): 33 | self.git("cherry-pick", "--abort") 34 | util.view.refresh_gitsavvy_interfaces(self.window, refresh_sidebar=True) 35 | 36 | 37 | class gs_cherry_pick_continue(GsWindowCommand): 38 | @on_worker 39 | def run(self): 40 | self.git("cherry-pick", "--continue") 41 | util.view.refresh_gitsavvy_interfaces(self.window, refresh_sidebar=True) 42 | 43 | 44 | class gs_cherry_pick_skip(GsWindowCommand): 45 | @on_worker 46 | def run(self): 47 | self.git("cherry-pick", "--skip") 48 | util.view.refresh_gitsavvy_interfaces(self.window, refresh_sidebar=True) 49 | -------------------------------------------------------------------------------- /core/commands/config.py: -------------------------------------------------------------------------------- 1 | import sublime_plugin 2 | 3 | import os 4 | 5 | from GitSavvy.core.git_command import GitCommand 6 | 7 | 8 | __all__ = ( 9 | "gs_open_repo_config", 10 | "gs_open_repo_exclude", 11 | ) 12 | 13 | 14 | class gs_open_repo_config(sublime_plugin.WindowCommand, GitCommand): 15 | def run(self) -> None: 16 | try: 17 | repo_path = self.get_repo_path() 18 | except ValueError: 19 | self.window.status_message("No .git repo found") 20 | return 21 | 22 | wanted_file = os.path.join(repo_path, '.git', 'config') 23 | if not os.path.exists(wanted_file): 24 | self.window.status_message("No config file found for {}".format(repo_path)) 25 | return 26 | 27 | self.window.open_file(wanted_file) 28 | 29 | 30 | class gs_open_repo_exclude(sublime_plugin.WindowCommand, GitCommand): 31 | def run(self) -> None: 32 | try: 33 | repo_path = self.get_repo_path() 34 | except ValueError: 35 | self.window.status_message("No .git repo found") 36 | return 37 | 38 | wanted_file = os.path.join(repo_path, '.git', 'info', 'exclude') 39 | if not os.path.exists(wanted_file): 40 | self.window.status_message("No exclude file found for {}".format(repo_path)) 41 | return 42 | 43 | self.window.open_file(wanted_file) 44 | -------------------------------------------------------------------------------- /core/commands/context_menu.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | 3 | import sublime 4 | 5 | from GitSavvy.core.base_commands import GsTextCommand 6 | 7 | from typing import Callable, List, Optional, TypeVar 8 | T = TypeVar("T") 9 | 10 | __all__ = ( 11 | "gs_ctx_line_history", 12 | "gs_ctx_pick_axe", 13 | "gs_ctx_stage_hunk", 14 | ) 15 | 16 | 17 | # Provide a `CommandContext` as the global `Context` which 18 | # is valid for this exact "runtime-task". This is to speed-up 19 | # the preconditions in `is_enabled` and `is_visible`. 20 | 21 | def cached_property(fn: Callable[..., T]) -> T: 22 | return property(lru_cache(1)(fn)) # type: ignore[return-value] 23 | 24 | 25 | class CommandContext: 26 | def __init__(self, cmd: GsTextCommand): 27 | self._cmd = cmd 28 | 29 | @cached_property 30 | def enabled(self) -> bool: 31 | return not self._cmd.savvy_settings.get("disable_context_menus") 32 | 33 | @cached_property 34 | def sel(self) -> List[sublime.Region]: 35 | return list(self._cmd.view.sel()) 36 | 37 | @cached_property 38 | def repo_path(self) -> Optional[str]: 39 | return self._cmd.find_repo_path() 40 | 41 | @cached_property 42 | def file_path(self) -> Optional[str]: 43 | return self._cmd.file_path 44 | 45 | 46 | Context = None 47 | 48 | 49 | def get_context(self) -> CommandContext: 50 | global Context 51 | if not Context: 52 | Context = CommandContext(self) 53 | sublime.set_timeout(reset_context) 54 | 55 | return Context 56 | 57 | 58 | def reset_context(): 59 | global Context 60 | Context = None 61 | 62 | 63 | class gs_ctx_line_history(GsTextCommand): 64 | def is_enabled(self) -> bool: 65 | ctx = get_context(self) 66 | return bool( 67 | ctx.sel 68 | and ctx.repo_path 69 | ) 70 | 71 | def is_visible(self) -> bool: 72 | ctx = get_context(self) 73 | return ctx.enabled and bool(ctx.repo_path) 74 | 75 | def run(self, edit) -> None: 76 | self.view.run_command("gs_line_history") 77 | 78 | 79 | class gs_ctx_stage_hunk(GsTextCommand): 80 | def is_enabled(self) -> bool: 81 | ctx = get_context(self) 82 | return bool( 83 | ctx.sel 84 | and ctx.repo_path 85 | and self.view.file_name() 86 | and not self.view.is_dirty() 87 | ) 88 | 89 | def is_visible(self) -> bool: 90 | ctx = get_context(self) 91 | return ctx.enabled and bool(ctx.repo_path) 92 | 93 | def run(self, edit) -> None: 94 | self.view.run_command("gs_stage_hunk") 95 | 96 | 97 | class gs_ctx_pick_axe(GsTextCommand): 98 | def is_enabled(self) -> bool: 99 | ctx = get_context(self) 100 | return bool( 101 | ctx.sel 102 | and ctx.repo_path 103 | and all(self.view.substr(r).strip() for r in ctx.sel) 104 | ) 105 | 106 | def is_visible(self) -> bool: 107 | ctx = get_context(self) 108 | return ctx.enabled and bool(ctx.repo_path) 109 | 110 | def run(self, edit) -> None: 111 | self.view.run_command("gs_graph_pickaxe") 112 | -------------------------------------------------------------------------------- /core/commands/custom.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from sublime_plugin import WindowCommand 3 | 4 | from ..git_command import GitCommand 5 | from ..runtime import enqueue_on_worker 6 | from ..ui_mixins.input_panel import show_single_line_input_panel 7 | from ..view import replace_view_content 8 | from ...common import util 9 | from GitSavvy.core.runtime import run_new_daemon_thread 10 | 11 | 12 | __all__ = ( 13 | "gs_custom", 14 | ) 15 | 16 | 17 | class gs_custom(WindowCommand, GitCommand): 18 | 19 | """ 20 | Run the specified custom command asynchronously. 21 | """ 22 | 23 | def run(self, **kwargs): 24 | args = kwargs.get('args') 25 | if not args: 26 | sublime.error_message("Custom command must provide args.") 27 | return 28 | 29 | # prompt for custom command argument 30 | if '{PROMPT_ARG}' in args: 31 | prompt_msg = kwargs.pop("prompt_msg", "Command argument: ") 32 | return show_single_line_input_panel( 33 | prompt_msg, 34 | "", 35 | lambda arg: self.run_impl(custom_argument=arg, **kwargs) 36 | ) 37 | 38 | self.run_impl(**kwargs) 39 | 40 | def run_impl( 41 | self, 42 | output_to_panel=False, 43 | output_to_buffer=False, 44 | args=None, 45 | start_msg="Starting custom command...", 46 | complete_msg="Completed custom command.", 47 | syntax=None, 48 | run_in_thread=False, 49 | custom_argument=None, 50 | custom_environ=None, 51 | ): 52 | 53 | for idx, arg in enumerate(args): 54 | if arg == "{REPO_PATH}": 55 | args[idx] = self.repo_path 56 | elif arg == "{FILE_PATH}": 57 | args[idx] = self.file_path 58 | elif arg == "{PROMPT_ARG}": 59 | args[idx] = custom_argument 60 | 61 | def program(): 62 | self.window.status_message(start_msg) 63 | stdout = self.git(*args, custom_environ=custom_environ) 64 | self.window.status_message(complete_msg) 65 | 66 | if output_to_panel: 67 | util.log.display_panel(self.window, stdout) 68 | if output_to_buffer: 69 | view = self.window.new_file() 70 | view.set_scratch(True) 71 | if syntax: 72 | view.set_syntax_file(syntax) 73 | replace_view_content(view, stdout.replace("\r", "\n")) 74 | 75 | util.view.refresh_gitsavvy_interfaces(self.window) 76 | 77 | if run_in_thread: 78 | run_new_daemon_thread(program) 79 | else: 80 | enqueue_on_worker(program) 81 | -------------------------------------------------------------------------------- /core/commands/fetch.py: -------------------------------------------------------------------------------- 1 | from ..runtime import on_worker 2 | from ...common import util 3 | from ..ui_mixins.quick_panel import show_remote_panel 4 | from GitSavvy.core.base_commands import ask_for_branch, GsWindowCommand 5 | 6 | 7 | __all__ = ( 8 | "gs_fetch", 9 | "gs_ff_update_branch" 10 | ) 11 | 12 | 13 | from GitSavvy.core.base_commands import Args, GsCommand, Kont 14 | 15 | 16 | def ask_for_remote(cmd, args, done): 17 | # type: (GsCommand, Args, Kont) -> None 18 | show_remote_panel(done, allow_direct=True, show_option_all=True) 19 | 20 | 21 | class gs_fetch(GsWindowCommand): 22 | """ 23 | Display a panel of all git remotes for active repository and 24 | do a `git fetch` asynchronously. 25 | """ 26 | defaults = { 27 | "remote": ask_for_remote 28 | } 29 | 30 | @on_worker 31 | def run(self, remote, refspec=None): 32 | fetch_all = remote == "" 33 | if fetch_all: 34 | self.window.status_message("Start fetching all remotes...") 35 | else: 36 | self.window.status_message("Start fetching {}...".format(remote)) 37 | 38 | self.fetch(None if fetch_all else remote, refspec) 39 | self.window.status_message("Fetch complete.") 40 | util.view.refresh_gitsavvy_interfaces(self.window) 41 | 42 | 43 | class gs_ff_update_branch(GsWindowCommand): 44 | defaults = { 45 | "branch": ask_for_branch(local_branches_only=True) 46 | } 47 | 48 | @on_worker 49 | def run(self, branch): 50 | local_branch = self.get_local_branch_by_name(branch) 51 | if not local_branch: 52 | raise RuntimeError( 53 | "repo and view inconsistent. " 54 | "can't fetch more info about branch {}" 55 | .format(branch) 56 | ) 57 | 58 | if not local_branch.upstream: 59 | self.window.status_message("{} has no upstream set.".format(branch)) 60 | return 61 | 62 | self.window.run_command("gs_fetch", { 63 | "remote": local_branch.upstream.remote, 64 | "refspec": "{}:{}".format(local_branch.upstream.branch, branch) 65 | }) 66 | -------------------------------------------------------------------------------- /core/commands/fixup.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import re 3 | from .log import gs_log_current_branch 4 | from ...common import util 5 | 6 | fixup_command = re.compile("^fixup! (.*)") 7 | 8 | 9 | class GsFixupFromStageCommand(gs_log_current_branch): 10 | def run(self): # type: ignore[override] 11 | status = self.get_working_dir_status() 12 | if status.unstaged_files or status.merge_conflicts: 13 | sublime.message_dialog( 14 | "Unable to perform rebase actions while repo is in unclean state." 15 | ) 16 | return 17 | if not status.staged_files: 18 | sublime.message_dialog("No staged files.") 19 | return 20 | super().run() 21 | 22 | def auto_squash(self, commit_chain): 23 | fixup_idx = len(commit_chain) - 1 24 | msg = commit_chain[fixup_idx].msg 25 | m = fixup_command.match(msg) 26 | if m: 27 | orig_msg = m.group(1) 28 | orig_commit_indx = fixup_idx - 1 29 | while orig_commit_indx >= 0: 30 | if commit_chain[orig_commit_indx].msg.startswith(orig_msg): 31 | break 32 | orig_commit_indx = orig_commit_indx - 1 33 | 34 | if orig_commit_indx >= 0: 35 | commit_chain.insert(orig_commit_indx + 1, commit_chain.pop(fixup_idx)) 36 | original_commit = commit_chain[orig_commit_indx] 37 | next_commit = commit_chain[orig_commit_indx + 1] 38 | original_commit.do_commit = False 39 | next_commit.msg = original_commit.msg 40 | next_commit.datetime = original_commit.datetime 41 | next_commit.author = original_commit.author 42 | next_commit.modified = True 43 | 44 | return commit_chain 45 | 46 | def do_action(self, commit_hash, **kwargs): 47 | commit = self.git("rev-parse", commit_hash).strip() 48 | self.git("commit", "--fixup", commit) 49 | try: 50 | base_commit = self.git("rev-parse", "{}~1".format(commit)).strip() 51 | entries = self.log_rebase(base_commit, preserve=True) 52 | commit_chain = self.auto_squash(self.perpare_rewrites(entries)) 53 | 54 | self.rewrite_active_branch(base_commit, commit_chain) 55 | except Exception as e: 56 | self.git("reset", "--soft", "HEAD^") 57 | sublime.message_dialog("Error encountered. Cannot autosquash fixup.") 58 | raise e 59 | finally: 60 | util.view.refresh_gitsavvy(self.window.active_view()) 61 | 62 | 63 | class GsQuickStageCurrentFileAndFixupCommand(GsFixupFromStageCommand): 64 | def run(self): # type: ignore[override] 65 | self.git("add", "--", self.file_path) 66 | super().run() 67 | -------------------------------------------------------------------------------- /core/commands/merge.py: -------------------------------------------------------------------------------- 1 | from ...common import util 2 | from GitSavvy.core.base_commands import ask_for_branch, GsWindowCommand 3 | from GitSavvy.core.utils import show_noop_panel, show_panel 4 | from GitSavvy.core.runtime import on_worker 5 | 6 | 7 | __all__ = ( 8 | "gs_merge", 9 | "gs_merge_abort", 10 | "gs_merge_continue", 11 | "gs_restart_merge_for_file", 12 | ) 13 | 14 | 15 | class gs_merge(GsWindowCommand): 16 | 17 | """ 18 | Display a list of branches available to merge against the active branch. 19 | When selected, perform merge with specified branch. 20 | """ 21 | 22 | defaults = { 23 | "branch": ask_for_branch(ignore_current_branch=True, merged=False), 24 | } 25 | 26 | @on_worker 27 | def run(self, branch): 28 | try: 29 | self.git("merge", branch) 30 | finally: 31 | util.view.refresh_gitsavvy_interfaces(self.window, refresh_sidebar=True) 32 | 33 | 34 | class gs_merge_abort(GsWindowCommand): 35 | 36 | """ 37 | Reset all files to pre-merge conditions, and abort the merge. 38 | """ 39 | 40 | @on_worker 41 | def run(self): 42 | self.git("merge", "--abort") 43 | util.view.refresh_gitsavvy_interfaces(self.window, refresh_sidebar=True) 44 | 45 | 46 | class gs_merge_continue(GsWindowCommand): 47 | 48 | """ 49 | Continue an ongoing merge. Here for completeness as a user could just commit 50 | as well. 51 | """ 52 | 53 | @on_worker 54 | def run(self): 55 | self.git("merge", "--continue") 56 | util.view.refresh_gitsavvy_interfaces(self.window, refresh_sidebar=True) 57 | 58 | 59 | class gs_restart_merge_for_file(GsWindowCommand): 60 | 61 | """ 62 | Reset a single file to pre-merge condition, but do not abort the merge. 63 | """ 64 | 65 | def run(self): 66 | paths = self.conflicting_files_() 67 | if not paths: 68 | show_noop_panel( 69 | self.window, "There are no files which have or had merge conflicts." 70 | ) 71 | 72 | def on_done(index): 73 | fpath = paths[index] 74 | self.git("checkout", "-m", "--", fpath) 75 | 76 | util.view.refresh_gitsavvy_interfaces(self.window) 77 | 78 | show_panel(self.window, paths, on_done) 79 | -------------------------------------------------------------------------------- /core/commands/mv.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import os 3 | import sublime 4 | from sublime_plugin import WindowCommand 5 | 6 | from ..git_command import GitCommand 7 | from ..ui_mixins.input_panel import show_single_line_input_panel 8 | 9 | 10 | class GsMvCurrentFileCommand(WindowCommand, GitCommand): 11 | 12 | """ 13 | Prompt the user for a new name for the current file. 14 | """ 15 | 16 | def run(self): 17 | if self.file_path: 18 | parent, base_name = os.path.split(self.file_path) 19 | on_done = functools.partial( 20 | self.on_done, 21 | self.file_path, parent, base_name) 22 | v = show_single_line_input_panel( 23 | "New Name:", base_name, 24 | on_done, None, None) 25 | name, ext = os.path.splitext(base_name) 26 | v.sel().clear() 27 | v.sel().add(sublime.Region(0, len(name))) 28 | 29 | def on_done(self, file_path, parent, base_name, new_name): 30 | if new_name == base_name: 31 | return 32 | new_path = os.path.join(parent, new_name) 33 | 34 | self.git("mv", file_path, new_path) 35 | v = self.window.find_open_file(file_path) 36 | if v: 37 | v.retarget(new_path) 38 | -------------------------------------------------------------------------------- /core/commands/pull.py: -------------------------------------------------------------------------------- 1 | from ...common import util 2 | from GitSavvy.core.base_commands import GsWindowCommand, ask_for_branch 3 | from GitSavvy.core.fns import flatten, unique 4 | from GitSavvy.core.runtime import on_worker 5 | 6 | from typing import Callable, List, Optional, Sequence 7 | from ..git_mixins.branches import Branch 8 | 9 | 10 | __all__ = ( 11 | "gs_pull", 12 | "gs_pull_from_branch", 13 | ) 14 | 15 | 16 | class GsPullBase(GsWindowCommand): 17 | def do_pull(self, remote, remote_branch, rebase): 18 | # type: (str, str, Optional[bool]) -> None 19 | """ 20 | Perform `git pull remote branch`. 21 | """ 22 | self.window.status_message("Starting pull...") 23 | output = self.pull(remote, remote_branch, rebase).strip() 24 | self.window.status_message( 25 | output if output == "Already up to date." else "Pull complete." 26 | ) 27 | util.view.refresh_gitsavvy(self.window.active_view()) 28 | 29 | 30 | class gs_pull(GsPullBase): 31 | """ 32 | Pull from remote tracking branch if it is found. Otherwise, use `gs_pull_from_branch`. 33 | """ 34 | 35 | @on_worker 36 | def run(self, rebase=None): 37 | upstream = self.get_upstream_for_active_branch() 38 | if upstream: 39 | self.do_pull(upstream.remote, upstream.branch, rebase) 40 | else: 41 | self.window.run_command("gs_pull_from_branch", {"rebase": rebase}) 42 | 43 | 44 | ask_for_branch_ = ask_for_branch( 45 | ask_remote_first=False, 46 | ignore_current_branch=True, 47 | memorize_key="last_branch_used_to_pull_from" 48 | ) 49 | 50 | 51 | class gs_pull_from_branch(GsPullBase): 52 | """ 53 | Through a series of panels, allow the user to pull from a branch. 54 | """ 55 | defaults = { 56 | "branch": ask_for_branch_ 57 | } 58 | 59 | @on_worker 60 | def run(self, branch, rebase=None): 61 | # type: (str, bool) -> None 62 | sources: Sequence[Callable[[], List[Branch]]] = ( 63 | # Typically, `ask_for_branch_`s `show_branch_panel` has just called 64 | # `get_branches` so the cached value in the store should be fresh 65 | # and good to go. 66 | lambda: self.current_state().get("branches", []), 67 | self.get_branches, 68 | ) 69 | branches = unique(flatten(getter() for getter in sources)) 70 | for branch_ in branches: 71 | if branch_.canonical_name == branch: 72 | self.do_pull(branch_.remote or ".", branch_.name, rebase) 73 | break 74 | else: 75 | self.window.status_message( 76 | f"fatal: the name '{branch}' is not in the list of the current branches") 77 | -------------------------------------------------------------------------------- /core/commands/quick_commit.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from sublime_plugin import WindowCommand 3 | 4 | from ..git_command import GitCommand 5 | from ...common import util 6 | from ..ui_mixins.input_panel import show_single_line_input_panel 7 | 8 | 9 | COMMIT_MSG_PROMPT = "Commit message:" 10 | 11 | 12 | class GsQuickCommitCommand(WindowCommand, GitCommand): 13 | 14 | """ 15 | Present the user with a input panel where they can enter a commit message. 16 | Once provided, perform a commit with that message. 17 | """ 18 | 19 | def run(self): 20 | show_single_line_input_panel( 21 | COMMIT_MSG_PROMPT, 22 | "", 23 | lambda msg: sublime.set_timeout_async(lambda: self.on_done(msg), 0) 24 | ) 25 | 26 | def on_done(self, commit_message): 27 | self.window.status_message("Commiting...") 28 | self.git("commit", "-q", "-F", "-", stdin=commit_message) 29 | self.window.status_message("Committed successfully.") 30 | util.view.refresh_gitsavvy(self.window.active_view()) 31 | 32 | 33 | class GsQuickStageCurrentFileCommitCommand(WindowCommand, GitCommand): 34 | 35 | """ 36 | Present the user with a input panel where they can enter a commit message. 37 | Once provided, stage the current file and perform a commit with the 38 | provided message. 39 | """ 40 | 41 | def run(self): 42 | show_single_line_input_panel( 43 | COMMIT_MSG_PROMPT, 44 | "", 45 | lambda msg: sublime.set_timeout_async(lambda: self.on_done(msg), 0) 46 | ) 47 | 48 | def on_done(self, commit_message): 49 | self.window.status_message("Commiting...") 50 | self.git("add", "--", self.file_path) 51 | self.git("commit", "-q", "-F", "-", stdin=commit_message) 52 | self.window.status_message("Committed successfully.") 53 | util.view.refresh_gitsavvy(self.window.active_view()) 54 | -------------------------------------------------------------------------------- /core/commands/reflog.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | from ..ui_mixins.quick_panel import show_paginated_panel 4 | from GitSavvy.core.base_commands import GsWindowCommand 5 | 6 | 7 | __all__ = ( 8 | "gs_ref_log", 9 | ) 10 | 11 | 12 | class RefLogMixin(GsWindowCommand): 13 | 14 | _limit = 6000 15 | 16 | def run(self): 17 | sublime.set_timeout_async(self.run_async) 18 | 19 | def run_async(self): 20 | show_paginated_panel( 21 | self.reflog_generator(limit=self._limit), self.on_done, limit=self._limit) 22 | 23 | def on_done(self, commit): 24 | if commit: 25 | self.do_action(commit) 26 | 27 | def do_action(self, commit_hash): 28 | self.window.run_command("gs_log_action", { 29 | "commit_hash": commit_hash 30 | }) 31 | 32 | 33 | class gs_ref_log(RefLogMixin): 34 | pass 35 | -------------------------------------------------------------------------------- /core/commands/remote.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | from . import init 4 | from ...common import util 5 | from ..ui_mixins.quick_panel import show_remote_panel 6 | from ..ui_mixins.input_panel import show_single_line_input_panel 7 | from GitSavvy.core.runtime import run_on_new_thread 8 | from GitSavvy.core.base_commands import GsWindowCommand 9 | 10 | 11 | __all__ = ( 12 | "gs_remote_add", 13 | "gs_remote_remove", 14 | "gs_remote_rename", 15 | ) 16 | 17 | 18 | class gs_remote_add(GsWindowCommand): 19 | """ 20 | Add remotes 21 | """ 22 | 23 | def run(self, url=None, set_as_push_default=False): 24 | self.set_as_push_default = set_as_push_default 25 | if url: 26 | self.on_enter_remote(url) 27 | else: 28 | clip_content = sublime.get_clipboard(256).strip() 29 | show_single_line_input_panel( 30 | "Remote URL", 31 | init.parse_url_from_clipboard(clip_content), 32 | self.on_enter_remote) 33 | 34 | def on_enter_remote(self, input_url): 35 | self.url = input_url 36 | owner = self.username_from_url(input_url) 37 | 38 | show_single_line_input_panel("Remote name", owner, self.on_enter_name) 39 | 40 | def on_enter_name(self, remote_name): 41 | self.git("remote", "add", remote_name, self.url) 42 | if self.set_as_push_default: 43 | run_on_new_thread(self.git, "config", "--local", "gitsavvy.pushdefault", remote_name) 44 | self.update_store({"last_remote_used_for_push": remote_name}) 45 | 46 | if self.savvy_settings.get("fetch_new_remotes", True) or sublime.ok_cancel_dialog( 47 | "Your remote was added successfully. " 48 | "Would you like to fetch from this remote?" 49 | ): 50 | self.window.run_command("gs_fetch", {"remote": remote_name}) 51 | 52 | 53 | class gs_remote_remove(GsWindowCommand): 54 | """ 55 | Remove remotes 56 | """ 57 | 58 | def run(self): 59 | show_remote_panel(self.on_remote_selection, show_url=True) 60 | 61 | @util.actions.destructive(description="remove a remote") 62 | def on_remote_selection(self, remote): 63 | self.git("remote", "remove", remote) 64 | util.view.refresh_gitsavvy_interfaces(self.window, refresh_status_bar=False) 65 | 66 | 67 | class gs_remote_rename(GsWindowCommand): 68 | """ 69 | Reame remotes 70 | """ 71 | 72 | def run(self): 73 | show_remote_panel(self.on_remote_selection, show_url=True) 74 | 75 | def on_remote_selection(self, remote): 76 | self.remote = remote 77 | show_single_line_input_panel("New name", remote, self.on_enter_name) 78 | 79 | def on_enter_name(self, new_name): 80 | if not new_name: 81 | return 82 | if new_name == self.remote: 83 | return 84 | self.git("remote", "rename", self.remote, new_name) 85 | self.window.status_message("remote {} was renamed as {}.".format(self.remote, new_name)) 86 | util.view.refresh_gitsavvy_interfaces(self.window, refresh_status_bar=False) 87 | -------------------------------------------------------------------------------- /core/commands/reset.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from .log import LogMixin 3 | from .reflog import RefLogMixin 4 | from ...common import util 5 | from ..ui_mixins.quick_panel import show_branch_panel 6 | from GitSavvy.core.base_commands import GsWindowCommand 7 | 8 | 9 | __all__ = ( 10 | "gs_reset", 11 | "gs_reset_branch", 12 | "gs_reset_reflog", 13 | ) 14 | 15 | 16 | GIT_RESET_MODES = [ 17 | # See analysis at 18 | # http://stackoverflow.com/questions/34149356/what-exactly-is-the-difference-between-all-the-git-reset-modes/34155307#34155307 19 | ["--soft", "stage differences (safe)"], 20 | ["--mixed", "unstage differences (safe)"], 21 | ["--hard", "discard uncommitted changes, overwrite working dir (unsafe)"], 22 | ["--keep", "keep uncommitted changes, update working dir (safe)"], 23 | ["--merge", "discard staged, keep unstaged, update working (abort if unsafe)"] 24 | ] 25 | MODES = [mode for mode, _ in GIT_RESET_MODES] 26 | 27 | 28 | class ResetMixin(GsWindowCommand): 29 | 30 | def do_action(self, commit_hash, **kwargs): 31 | if not commit_hash: 32 | return 33 | self._selected_hash = commit_hash 34 | 35 | use_reset_mode = self.savvy_settings.get("use_reset_mode") 36 | last_reset_mode_used = \ 37 | self.current_state().get("last_reset_mode_used", use_reset_mode) 38 | reset_modes = ( 39 | GIT_RESET_MODES 40 | + ( 41 | [[use_reset_mode, ""]] 42 | if use_reset_mode and use_reset_mode not in MODES 43 | else [] 44 | ) 45 | ) 46 | try: 47 | selected_index = ( 48 | [m for m, _ in reset_modes] 49 | .index(last_reset_mode_used) 50 | ) 51 | except ValueError: 52 | selected_index = -1 53 | 54 | def on_done(index): 55 | if index == -1: 56 | return 57 | self.on_reset(reset_modes[index][0].strip()) 58 | 59 | self.window.show_quick_panel( 60 | reset_modes, 61 | on_done, 62 | flags=sublime.MONOSPACE_FONT, 63 | selected_index=selected_index 64 | ) 65 | 66 | def on_reset(self, reset_mode): 67 | # Split the reset mode to support multiple args, e.g. "--mixed -N" 68 | args = reset_mode.split() + [self._selected_hash] 69 | 70 | def do_reset(): 71 | self.update_store({"last_reset_mode_used": reset_mode}) 72 | self.git("reset", *args) 73 | util.view.refresh_gitsavvy_interfaces(self.window, refresh_sidebar=True) 74 | 75 | if reset_mode == "--hard": 76 | util.actions.destructive("perform a hard reset")(do_reset)() 77 | else: 78 | do_reset() 79 | 80 | 81 | class gs_reset(ResetMixin, LogMixin): 82 | pass 83 | 84 | 85 | class gs_reset_branch(ResetMixin): 86 | def run(self, **kwargs): 87 | show_branch_panel(self.on_branch_selection) 88 | 89 | def on_branch_selection(self, branch): 90 | self.do_action(branch) 91 | 92 | 93 | class gs_reset_reflog(ResetMixin, RefLogMixin): 94 | pass 95 | -------------------------------------------------------------------------------- /core/commands/revert.py: -------------------------------------------------------------------------------- 1 | from sublime_plugin import WindowCommand 2 | 3 | from .log import LogMixin 4 | from ..git_command import GitCommand 5 | from ...common import util 6 | from GitSavvy.core.base_commands import GsWindowCommand 7 | from GitSavvy.core.runtime import on_worker 8 | 9 | 10 | __all__ = ( 11 | "gs_revert_commit", 12 | "gs_revert_abort", 13 | "gs_revert_continue", 14 | "gs_revert_skip", 15 | ) 16 | 17 | 18 | class gs_revert_commit(LogMixin, WindowCommand, GitCommand): 19 | @on_worker 20 | def do_action(self, commit_hash, **kwargs): 21 | try: 22 | self.git("revert", *(commit_hash if isinstance(commit_hash, list) else [commit_hash])) 23 | finally: 24 | util.view.refresh_gitsavvy(self.window.active_view(), refresh_sidebar=True) 25 | 26 | 27 | class gs_revert_abort(GsWindowCommand): 28 | @on_worker 29 | def run(self): 30 | try: 31 | self.git("revert", "--abort") 32 | finally: 33 | util.view.refresh_gitsavvy_interfaces(self.window, refresh_sidebar=True) 34 | 35 | 36 | class gs_revert_continue(GsWindowCommand): 37 | @on_worker 38 | def run(self): 39 | try: 40 | self.git("revert", "--continue") 41 | finally: 42 | util.view.refresh_gitsavvy_interfaces(self.window, refresh_sidebar=True) 43 | 44 | 45 | class gs_revert_skip(GsWindowCommand): 46 | @on_worker 47 | def run(self): 48 | try: 49 | self.git("revert", "--skip") 50 | finally: 51 | util.view.refresh_gitsavvy_interfaces(self.window, refresh_sidebar=True) 52 | -------------------------------------------------------------------------------- /core/commands/stage_diff.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implements a special view that displays an editable diff of unstaged changes. 3 | """ 4 | 5 | import os 6 | 7 | import sublime 8 | from sublime_plugin import WindowCommand, TextCommand 9 | 10 | from ..git_command import GitCommand 11 | from ..view import replace_view_content 12 | from ...common import util 13 | 14 | 15 | TITLE = "STAGE-DIFF: {}" 16 | MESSAGE = "Press {}-Enter to apply the diff. Close the window to cancel.".format(util.super_key) 17 | 18 | 19 | class GsStageDiffCommand(WindowCommand, GitCommand): 20 | 21 | """ 22 | Create a new view to display the project's unstaged changes. 23 | """ 24 | 25 | def run(self): 26 | repo_path = self.repo_path 27 | stage_diff_view = util.view.create_scratch_view(self.window, "git_stage_diff", { 28 | "syntax": "Packages/GitSavvy/syntax/diff.sublime_syntax", 29 | "title": TITLE.format(os.path.basename(repo_path)), 30 | "read_only": False, 31 | "git_savvy.repo_path": repo_path, 32 | }) 33 | 34 | stdout = self.git("diff", "--no-color") 35 | content = MESSAGE + "\n\n" + stdout 36 | replace_view_content(stage_diff_view, content) 37 | 38 | 39 | class GsStageDiffApplyCommand(TextCommand, GitCommand): 40 | 41 | """ 42 | Apply the commit as it is presented in the view to the index. Then close the view. 43 | """ 44 | 45 | def run(self, edit): 46 | diff_content = self.view.substr(sublime.Region(0, self.view.size())) 47 | self.git("apply", "--cached", "-", stdin=diff_content) 48 | self.view.close() 49 | -------------------------------------------------------------------------------- /core/commands/status_bar.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from sublime_plugin import TextCommand, EventListener 3 | 4 | from ..git_command import GitCommand 5 | from ..runtime import run_when_worker_is_idle, throttled 6 | from GitSavvy.core import store 7 | 8 | 9 | STATUSBAR_KEY = "gitsavvy-repo-status" 10 | 11 | 12 | class GsStatusBarEventListener(EventListener): 13 | def on_activated(self, view): 14 | view.run_command("gs_draw_status_bar") 15 | 16 | def on_post_save(self, view): 17 | view.run_command("gs_update_status") 18 | 19 | 20 | class gs_update_status(TextCommand, GitCommand): 21 | def run(self, edit): 22 | run_when_worker_is_idle(throttled(self.run_impl, self.view)) 23 | 24 | def run_impl(self, view): 25 | repo_path = self.find_repo_path() 26 | if repo_path: 27 | try: 28 | self.update_working_dir_status() 29 | except RuntimeError: 30 | # Although with `if repo_path` we have enough to make the 31 | # status call to git safe, the processing of the status 32 | # asks `self.repo_path` multiple times. 33 | # A user might have closed the view in between so we MUST 34 | # catch potential `RuntimeError`s. 35 | pass 36 | 37 | 38 | class gs_draw_status_bar(TextCommand, GitCommand): 39 | 40 | """ 41 | Update the short Git status in the Sublime status bar. 42 | """ 43 | 44 | def run(self, edit, repo_path=None): 45 | view = self.view 46 | if not self.savvy_settings.get("git_status_in_status_bar"): 47 | view.erase_status(STATUSBAR_KEY) 48 | return 49 | 50 | if not repo_path: 51 | repo_path = self.find_repo_path() 52 | if not repo_path: 53 | return 54 | elif repo_path != self.find_repo_path(): 55 | return 56 | 57 | try: 58 | short_status = store.current_state(repo_path)["short_status"] 59 | except Exception: 60 | ... 61 | else: 62 | view.set_status(STATUSBAR_KEY, short_status) 63 | 64 | 65 | def on_status_update(repo_path, state): 66 | view = sublime.active_window().active_view() 67 | if view: 68 | view.run_command("gs_draw_status_bar", {"repo_path": repo_path}) 69 | 70 | 71 | store.subscribe("*", {"short_status"}, on_status_update) 72 | -------------------------------------------------------------------------------- /core/exceptions.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from ..common import util 3 | 4 | 5 | from typing import Optional, Sequence 6 | 7 | 8 | class GitSavvyError(Exception): 9 | def __init__(self, msg, *, cmd=None, stdout="", stderr="", show_panel=True, window=None): 10 | # type: (str, Sequence[str], str, str, bool, Optional[sublime.Window]) -> None 11 | super(GitSavvyError, self).__init__(msg) 12 | self.message = msg 13 | self.cmd = cmd 14 | self.stdout = stdout 15 | self.stderr = stderr 16 | self.show_panel = show_panel 17 | self.window = window 18 | if msg: 19 | if show_panel: 20 | self.show_error_panel() 21 | util.debug.log_error(msg) 22 | 23 | def show_error_panel(self): 24 | util.log.display_panel(self.window or sublime.active_window(), self.message) 25 | 26 | 27 | class FailedGithubRequest(GitSavvyError): 28 | pass 29 | 30 | 31 | class FailedGitLabRequest(GitSavvyError): 32 | pass 33 | 34 | 35 | class DetachedView(RuntimeError): 36 | pass 37 | -------------------------------------------------------------------------------- /core/git_mixins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timbrel/GitSavvy/f0c9067eb061498ba7a07960e5cbaefb414b4994/core/git_mixins/__init__.py -------------------------------------------------------------------------------- /core/git_mixins/checkout_discard.py: -------------------------------------------------------------------------------- 1 | from GitSavvy.core.git_command import mixin_base 2 | 3 | 4 | class CheckoutDiscardMixin(mixin_base): 5 | 6 | def discard_all_unstaged(self): 7 | """ 8 | Any changes that are not staged or committed will be reverted 9 | to their state in HEAD. Any new files will be deleted. 10 | """ 11 | self.git("clean", "-df") 12 | self.git("checkout", "--", ".") 13 | 14 | def discard_untracked_file(self, *fpaths): 15 | """ 16 | Given a list of absolute paths or paths relative to the repo's root, 17 | remove the file or directory from the working tree. 18 | """ 19 | self.git("clean", "-df", "--", *fpaths) 20 | 21 | def checkout_file(self, *fpaths): 22 | """ 23 | Given a list of absolute paths or paths relative to the repo's root, 24 | discard any changes made to the file and revert it in the working 25 | directory to the state it is in HEAD. 26 | """ 27 | self.git("checkout", "--", *fpaths) 28 | 29 | def checkout_ref(self, ref, fpath=None): 30 | """ 31 | Given a ref (local branch, remote branch, tag, etc), check it out. 32 | """ 33 | if fpath: 34 | self.git("checkout", ref, "--", fpath) 35 | else: 36 | self.git("checkout", ref) 37 | -------------------------------------------------------------------------------- /core/git_mixins/ignore.py: -------------------------------------------------------------------------------- 1 | import os 2 | from ...common import util 3 | 4 | from GitSavvy.core.git_command import mixin_base 5 | from GitSavvy.core.utils import cache_in_store_as 6 | 7 | from typing import List 8 | 9 | 10 | linesep = None 11 | 12 | 13 | class IgnoreMixin(mixin_base): 14 | @cache_in_store_as("skipped_files") 15 | def get_skipped_files(self) -> List[str]: 16 | """Return all files for which skip-worktree is set.""" 17 | return [ 18 | file_path 19 | for line in self.git("ls-files", "-v").splitlines() 20 | if line.startswith("S") 21 | if (file_path := line[2:]) 22 | ] 23 | 24 | def set_skip_worktree(self, *file_paths: str) -> None: 25 | self.git("update-index", "--skip-worktree", *file_paths) 26 | 27 | def unset_skip_worktree(self, *file_paths: str) -> None: 28 | self.git("update-index", "--no-skip-worktree", *file_paths) 29 | 30 | def add_ignore(self, path_or_pattern): 31 | """ 32 | Add the provided relative path or pattern to the repo's `.gitignore` file. 33 | """ 34 | global linesep 35 | 36 | if not linesep: 37 | # Use native line ending on Windows only when `autocrlf` is set to `true`. 38 | if os.name == "nt": 39 | autocrlf = self.git("config", "--global", "core.autocrlf", 40 | throw_on_error=False).strip() == "true" 41 | linesep = os.linesep if autocrlf else "\n" 42 | else: 43 | linesep = os.linesep 44 | 45 | gitignore = os.path.join(self.repo_path, ".gitignore") 46 | if os.path.exists(gitignore): 47 | with util.file.safe_open(gitignore, "r", encoding="utf-8") as fp: 48 | ignore_lines = fp.read().splitlines() 49 | else: 50 | ignore_lines = [] 51 | 52 | ignore_lines += [path_or_pattern, ""] 53 | with util.file.safe_open(gitignore, "w", encoding="utf-8", newline=linesep) as fp: 54 | fp.write("\n".join(ignore_lines)) 55 | -------------------------------------------------------------------------------- /core/git_mixins/rebase.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from GitSavvy.core.fns import filter_ 4 | from GitSavvy.core.git_command import mixin_base 5 | 6 | 7 | from typing import List, Optional 8 | 9 | 10 | EXTRACT_BRANCH_NAME = re.compile(r'^[^[]+\[(.*?)(?:[\^\~]+[\d]*)*\]') 11 | 12 | 13 | class NearestBranchMixin(mixin_base): 14 | """ Provide reusable methods for detecting the nearest of a branch relatives """ 15 | 16 | def branch_relatives(self, branch): 17 | # type: (str) -> List[str] 18 | """ Get list of all relatives from ``git show-branch`` results """ 19 | output = self.git("show-branch", "--no-color") # type: str 20 | 21 | try: 22 | prelude, body = re.split(r'^-+$', output, flags=re.M) 23 | except ValueError: 24 | # If there is only one branch, git changes the output format 25 | # and omits the prelude and column indicator. 26 | lines = filter_(output.splitlines()) 27 | else: 28 | match = re.search(r'^(\s+)\*', prelude, re.M) 29 | if not match: 30 | print("branch {} not found in header information".format(branch)) 31 | return [] 32 | 33 | branch_column = len(match.group(1)) 34 | lines = ( 35 | line 36 | for line in filter_(body.splitlines()) 37 | if line[branch_column] != ' ' 38 | ) 39 | 40 | relatives = [] # type: List[str] 41 | for line in lines: 42 | match = EXTRACT_BRANCH_NAME.match(line) 43 | if match: 44 | branch_name = match.group(1) 45 | if branch_name != branch and branch_name not in relatives: 46 | relatives.append(branch_name) 47 | 48 | return relatives 49 | 50 | def nearest_branch(self, branch, default="master"): 51 | # type: (Optional[str], str) -> str 52 | """ 53 | Find the nearest commit in current branch history that exists 54 | on a different branch and return that branch name. 55 | 56 | If no such branch is found, return the given default ("master" if not 57 | specified). 58 | 59 | """ 60 | relatives = self.branch_relatives(branch or default) 61 | if not relatives: 62 | return default 63 | 64 | return relatives[0] 65 | -------------------------------------------------------------------------------- /core/git_mixins/stage_unstage.py: -------------------------------------------------------------------------------- 1 | from GitSavvy.core.git_command import mixin_base 2 | 3 | 4 | class StageUnstageMixin(mixin_base): 5 | 6 | def stage_file(self, *fpath, force=True): 7 | # type: (str, bool) -> None 8 | """ 9 | Given an absolute path or path relative to the repo's root, stage 10 | the file. 11 | """ 12 | # Ensure we don't run "add --all" without any paths which 13 | # would add everything 14 | if not fpath: 15 | return 16 | 17 | self.git( 18 | "add", 19 | "-f" if force else None, 20 | "--all", 21 | "--", 22 | *fpath 23 | ) 24 | 25 | def unstage_file(self, *fpath): 26 | # type: (str) -> None 27 | """ 28 | Given an absolute path or path relative to the repo's root, unstage 29 | the file. 30 | """ 31 | # Ensure we don't run "reset" without any paths which 32 | # would unstage everything 33 | if not fpath: 34 | return 35 | 36 | self.git("reset", "HEAD", "--", *fpath) 37 | 38 | def add_all_tracked_files(self): 39 | """ 40 | Add to index all files that have been deleted or modified, but not 41 | those that have been created. 42 | """ 43 | return self.git("add", "-u") 44 | 45 | def add_all_files(self): 46 | """ 47 | Add to index all files that have been deleted, modified, or 48 | created. 49 | """ 50 | return self.git("add", "-A") 51 | 52 | def unstage_all_files(self): 53 | """ 54 | Remove all staged files from the index. 55 | """ 56 | return self.git("reset") 57 | 58 | def intent_to_add(self, *file_paths: str) -> None: 59 | self.git("add", "--intent-to-add", "--", *file_paths) 60 | 61 | def undo_intent_to_add(self, *file_paths: str) -> None: 62 | # Ensure we don't run "reset" without any paths which 63 | # would unstage everything 64 | if not file_paths: 65 | return 66 | 67 | self.git("reset", "--", *file_paths) 68 | -------------------------------------------------------------------------------- /core/git_mixins/stash.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from GitSavvy.core.git_command import mixin_base 4 | from GitSavvy.core.utils import cache_in_store_as 5 | 6 | from typing import List, NamedTuple, Union 7 | StashId = Union[str, int] 8 | 9 | 10 | class Stash(NamedTuple): 11 | id: str 12 | description: str 13 | 14 | 15 | class StashMixin(mixin_base): 16 | 17 | @cache_in_store_as("stashes") 18 | def get_stashes(self): 19 | # type: () -> List[Stash] 20 | """ 21 | Return a list of stashes in the repo. 22 | """ 23 | stdout = self.git("stash", "list") 24 | stashes = [] 25 | for entry in stdout.split("\n"): 26 | if not entry: 27 | continue 28 | match = re.match("^stash@\\{(\\d+)}: (.*?: )?(.*)", entry) 29 | assert match 30 | num, _, description = match.groups() 31 | stashes.append(Stash(num, description)) 32 | 33 | return stashes 34 | 35 | def show_stash(self, id): 36 | # type: (StashId) -> str 37 | stash_name = "stash@{{{}}}".format(id) 38 | return self.git("stash", "show", "--no-color", "-p", stash_name) 39 | 40 | def apply_stash(self, id): 41 | # type: (StashId) -> None 42 | """ 43 | Apply stash with provided id. 44 | """ 45 | self.git("stash", "apply", "stash@{{{}}}".format(id)) 46 | 47 | def pop_stash(self, id): 48 | # type: (StashId) -> None 49 | """ 50 | Pop stash with provided id. 51 | """ 52 | self.git("stash", "pop", "stash@{{{}}}".format(id)) 53 | 54 | def create_stash(self, description, include_untracked=False): 55 | # type: (str, bool) -> None 56 | """ 57 | Create stash with provided description from working files. 58 | """ 59 | self.git("stash", "save", "-k", "-u" if include_untracked else None, description) 60 | 61 | def drop_stash(self, id): 62 | # type: (StashId) -> str 63 | """ 64 | Drop stash with provided id. 65 | """ 66 | return self.git("stash", "drop", "stash@{{{}}}".format(id)) 67 | -------------------------------------------------------------------------------- /core/interfaces/__init__.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from sublime_plugin import TextCommand 3 | 4 | from .status import * # noqa: F401, F403 5 | from .branch import * # noqa: F401, F403 6 | from .rebase import * # noqa: F401, F403 7 | from .tags import * # noqa: F401, F403 8 | from ..git_command import GitCommand 9 | 10 | 11 | class gs_tab_cycle(TextCommand, GitCommand): 12 | commands = { 13 | "status": "gs_show_status", 14 | "branch": "gs_show_branch", 15 | "rebase": "gs_show_rebase", 16 | "tags": "gs_show_tags", 17 | "graph": "gs_log_graph_tab_in" 18 | } 19 | 20 | def run(self, edit, source=None, target=None, reverse=False): 21 | to_load = target or self.get_next(source, reverse) 22 | if not to_load: 23 | return 24 | 25 | window = self.view.window() 26 | if window: 27 | window.run_command(self.commands[to_load]) 28 | if not self.view.settings().get("git_savvy.log_graph_view"): 29 | self.view.close() 30 | 31 | def get_next(self, source, reverse=False): 32 | tab_order = self.savvy_settings.get("tab_order") 33 | 34 | try: 35 | idx = tab_order.index(source) 36 | except ValueError: 37 | return None 38 | 39 | delta = (-1 if reverse else +1) 40 | next_idx = (idx + delta) % len(tab_order) 41 | return tab_order[next_idx] 42 | -------------------------------------------------------------------------------- /core/settings.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | 3 | import sublime 4 | import sublime_plugin 5 | 6 | from GitSavvy.core.fns import maybe 7 | 8 | 9 | __all__ = ( 10 | "ProjectFileChanges", 11 | ) 12 | 13 | from typing import Dict 14 | 15 | 16 | class GitSavvySettings: 17 | def __init__(self, window=None): 18 | # type: (sublime.Window) -> None 19 | self._window = window or sublime.active_window() 20 | self._global_settings = get_global_settings() 21 | 22 | def get(self, key, default=None): 23 | try: 24 | return get_project_settings(self._window)[key] 25 | except KeyError: 26 | return self._global_settings.get(key, default) 27 | 28 | def set(self, key, value): 29 | self._global_settings.set(key, value) 30 | 31 | 32 | CHANGE_COUNT = 0 33 | 34 | 35 | class ProjectFileChanges(sublime_plugin.EventListener): 36 | def on_post_save(self, view): 37 | # type: (sublime.View) -> None 38 | global CHANGE_COUNT 39 | file_path = view.file_name() 40 | if file_path and file_path.endswith(".sublime-project"): 41 | CHANGE_COUNT += 1 42 | 43 | 44 | def get_project_settings(window): 45 | # type: (sublime.Window) -> Dict 46 | global CHANGE_COUNT 47 | return _get_project_settings(window.id(), CHANGE_COUNT) 48 | 49 | 50 | @lru_cache(maxsize=16) 51 | def _get_project_settings(wid, _counter): 52 | # type: (sublime.WindowId, int) -> Dict 53 | window = sublime.Window(wid) 54 | project_data = window.project_data() 55 | if not project_data: 56 | return {} 57 | return project_data.get("settings", {}).get("GitSavvy", {}) 58 | 59 | 60 | @lru_cache(maxsize=1) 61 | def get_global_settings(): 62 | return GlobalSettings("GitSavvy.sublime-settings") 63 | 64 | 65 | class GlobalSettings: 66 | def __init__(self, name): 67 | self._settings = s = sublime.load_settings(name) 68 | s.clear_on_change(name) 69 | s.add_on_change(name, self._on_update) 70 | self._cache = {} 71 | 72 | def get(self, name, default=None): 73 | try: 74 | return self._cache[name] 75 | except KeyError: 76 | self._cache[name] = current_value = self._settings.get(name, default) # type: ignore[var-annotated] 77 | return current_value 78 | 79 | def set(self, name, value): 80 | self._settings.set(name, value) # implicitly calls `_on_update` to clear cache 81 | 82 | def _on_update(self): 83 | self._cache.clear() 84 | 85 | 86 | class SettingsMixin: 87 | _savvy_settings = None 88 | 89 | @property 90 | def savvy_settings(self): 91 | if not self._savvy_settings: 92 | window = self.some_window() 93 | self._savvy_settings = GitSavvySettings(window) 94 | return self._savvy_settings 95 | 96 | def some_window(self): 97 | # type: () -> sublime.Window 98 | return ( 99 | maybe(lambda: self.window) # type: ignore[attr-defined] 100 | or maybe(lambda: self.view.window()) # type: ignore[attr-defined] 101 | or sublime.active_window() 102 | ) 103 | -------------------------------------------------------------------------------- /core/store.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict, deque 2 | from functools import partial 3 | import threading 4 | import uuid 5 | 6 | from .utils import eat_but_log_errors 7 | 8 | 9 | from typing import ( 10 | AbstractSet, Callable, DefaultDict, Deque, Dict, List, Optional, Set, Tuple, TypedDict, 11 | TYPE_CHECKING 12 | ) 13 | 14 | if TYPE_CHECKING: 15 | from GitSavvy.core.git_mixins.active_branch import Commit 16 | from GitSavvy.core.git_mixins.branches import Branch 17 | from GitSavvy.core.git_mixins.stash import Stash 18 | from GitSavvy.core.git_mixins.tags import TagList 19 | from GitSavvy.core.git_mixins.status import HeadState, WorkingDirState 20 | 21 | class RepoStore(TypedDict, total=False): 22 | status: WorkingDirState 23 | head: HeadState 24 | long_status: str 25 | short_status: str 26 | branches: List[Branch] 27 | remotes: Dict[str, str] 28 | remotes_with_no_tags_set: Set[str] 29 | local_tags: TagList 30 | last_branches: Deque[Optional[str]] 31 | last_branch_used_to_pull_from: Optional[str] 32 | last_branch_used_to_rebase_from: Optional[str] 33 | last_remote_used: Optional[str] 34 | last_remote_used_for_push: Optional[str] 35 | last_remote_used_with_option_all: Optional[str] 36 | last_reset_mode_used: Optional[str] 37 | short_hash_length: int 38 | skipped_files: List[str] 39 | slow_repo: bool 40 | stashes: List[Stash] 41 | recent_commits: List[Commit] 42 | descriptions: Dict[str, str] 43 | default_graph_options: Dict[str, str] 44 | RepoPath = str 45 | SubscriberKey = str 46 | Keys = AbstractSet[str] 47 | 48 | 49 | def initial_state(): 50 | # type: () -> RepoStore 51 | return { 52 | "last_branches": deque([None] * 2, 2), 53 | } 54 | 55 | 56 | state = defaultdict(initial_state) # type: DefaultDict[RepoPath, RepoStore] 57 | subscribers = {} # type: Dict[SubscriberKey, Tuple[RepoPath, Keys, Callable]] 58 | 59 | lock = threading.Lock() 60 | 61 | 62 | def update_state(repo_path, partial_state): 63 | # type: (RepoPath, RepoStore) -> None 64 | with lock: 65 | state[repo_path].update(partial_state) 66 | notify_all(repo_path, partial_state.keys(), state[repo_path]) 67 | 68 | 69 | def notify_all(repo_path, updated_keys, current_state): 70 | # type: (RepoPath, Keys, RepoStore) -> None 71 | for (subscribed_repo_path, keys, fn) in subscribers.values(): 72 | if ( 73 | subscribed_repo_path in {repo_path, "*"} 74 | and updated_keys & keys 75 | ): 76 | with eat_but_log_errors(): 77 | fn(repo_path, current_state) 78 | 79 | 80 | def current_state(repo_path): 81 | # type: (RepoPath) -> RepoStore 82 | return state[repo_path] 83 | 84 | 85 | def subscribe(repo_path, keys, fn): 86 | # type: (RepoPath, Keys, Callable) -> Callable[[], None] 87 | key = uuid.uuid4().hex 88 | subscribers[key] = (repo_path, keys, fn) 89 | return partial(_unsubscribe, key) 90 | 91 | 92 | def _unsubscribe(key): 93 | # type: (SubscriberKey) -> None 94 | subscribers.pop(key, None) 95 | -------------------------------------------------------------------------------- /core/types.py: -------------------------------------------------------------------------------- 1 | 2 | # Use LineNo, ColNo for 1-based line column counting (like git or `window.open_file`), 3 | # use Row, Col for 0-based counting like Sublime's `view.rowcol`! 4 | LineNo = int 5 | ColNo = int 6 | Row = int 7 | Col = int 8 | -------------------------------------------------------------------------------- /core/ui_mixins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timbrel/GitSavvy/f0c9067eb061498ba7a07960e5cbaefb414b4994/core/ui_mixins/__init__.py -------------------------------------------------------------------------------- /core/ui_mixins/input_panel.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | 4 | from typing import Callable, Optional 5 | ValueCallback = Callable[[str], None] 6 | CancelCallback = Callable[[], None] 7 | 8 | 9 | def show_single_line_input_panel( 10 | caption, # type: str 11 | initial_text, # type: str 12 | on_done, # type: ValueCallback 13 | on_change=None, # type: Optional[ValueCallback] 14 | on_cancel=None, # type: Optional[CancelCallback] 15 | select_text=True # type: bool 16 | ): # type: (...) -> sublime.View 17 | window = sublime.active_window() 18 | v = window.show_input_panel(caption, initial_text, on_done, on_change, on_cancel) 19 | if select_text: 20 | v.run_command("select_all") 21 | v.settings().set("git_savvy.single_line_input_panel", True) 22 | return v 23 | -------------------------------------------------------------------------------- /dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "*": { 3 | ">=4000": [ 4 | "typing_extensions" 5 | ] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docs/debug.md: -------------------------------------------------------------------------------- 1 | # Debug 2 | 3 | If you're doing development on GitSavvy, the following commands may be useful. 4 | 5 | 6 | ## `GitSavvy: reload modules (debug)` 7 | 8 | This command will reload all GitSavvy-related Python modules and initiate a plugin reset for Sublime Text 3. Note that the editor's interface may become unresponsive for a second or two while the plugins are reloaded. However, this workflow is often preferable to closing and re-opening Sublime when testing changes to GitSavvy. 9 | 10 | This command will only have an effect if `dev_mode` is set to `true` in `GitSavvy` settings. 11 | 12 | 13 | ## `GitSavvy: enable logging` 14 | 15 | This will start tracking the inputs and outputs of all Git commands that are running under the hood. Aggregating this data can be useful for those wanting to learn how GitSavvy works, as well as for debugging purposes. 16 | 17 | **Note:** If you've logged a sequence of Git commands before, running this again will overwrite what was previously recorded. 18 | 19 | 20 | ## `GitSavvy: disable logging` 21 | 22 | This stops all recording Git command inputs and outputs, but does not destroy the record. 23 | 24 | ## `GitSavvy: view recorded log` 25 | 26 | Once you have started and stopped logging, this command will display the log in JSON format in a new scratch view. 27 | 28 | # Providing a Debug Log 29 | 30 | Ocasionally when creating a new issue in GitSavvy, you will be requested to provide a debug log. The above commands make it easy to do, by following these steps: 31 | 32 | 1. Open sublime, and get to the state just prior to running the failing command. 33 | 2. Open command palette, and run the command "GitSavvy: enable logging". 34 | 3. Perform the failing command. 35 | 4. Run the command "GitSavvy: disable logging" 36 | 5. Run the command "GitSavvy: view recorded log", 37 | save the file locally and attach it to your issue. 38 | 39 | _Note: Take care to remove any sensitive information that may be recorded in GitSavvy._ 40 | 41 | # Other tools / PATH issues / Different behavior from terminal and inside Sublime 42 | 43 | Please check the tool is in your PATH inside sublime. 44 | First find the path to where the tool binaries are, open a system terminal/shell and paste: 45 | 46 | which ; 47 | 48 | Check if that path is the `$PATH` inside sublime. To do that open the sublime console and paste in: 49 | 50 | from os import environ; environ['PATH'] 51 | 52 | If the tool path is missing from your sublime environment's `PATH` variable, you need to look into correcting the env variables available to sublime on launch. The unofficial docs mention how to set it globally for windows and mac in [the "Troubleshooting Build Systems" page][2]. Alternatively, you may want to look at the packages ["Environment Settings"][3] and ["Fix Mac Path"][4]. 53 | 54 | If, however, the path is in your sublime environment, please open an issue and include these values in an issue. 55 | 56 | [1]: https://github.com/timbrel/GitSavvy/issues/684#issuecomment-323579850 57 | [2]: http://docs.sublimetext.info/en/latest/reference/build_systems/troubleshooting.html 58 | [3]: https://packagecontrol.io/packages/Environment%20Settings 59 | [4]: https://packagecontrol.io/packages/Fix%20Mac%20Path 60 | -------------------------------------------------------------------------------- /docs/flow.md: -------------------------------------------------------------------------------- 1 | # git-flow 2 | 3 | Vincent Driessen's [git-flow](https://github.com/nvie/gitflow) extension is fully supported, allowing you to run flow commands with sublime commands. To enable `git-flow` integration you must set `show_git_flow_commands` to `true` in `GitSavvy` settings. 4 | 5 | Most commands attempt to mirror `git-flow` 's interface with the added ability to select a target from local branches/remotes. 6 | 7 | #### Notes 8 | - Requires _version **0.4.1**_ and above. 9 | - In some environments, like OS X, Sublime Text does not inherit the shell PATH environment value, and prevents `git-flow` extension from being located. This can be fixed with a plugin such as [SublimeFixMacPath](https://github.com/int3h/SublimeFixMacPath). 10 | 11 | ## `flow: init` 12 | 13 | A required step when you wish to setup a project to use `git-flow`. This will present a series of prompts to configure `git-flow` very much like the interactive shell command does. 14 | 15 | ## `flow: feature/release/hotfix/support start` 16 | 17 | When running this command, you will prompted first for the feature/release/hotfix/support name (without the prefix), and then the branch will be created and checked out. 18 | 19 | ## `flow: feature/release/hotfix/support finish` 20 | 21 | When running this command when an existing feature/release/hotfix/support branch is checked out, you will asked to confirm finish. Otherwise, you will be asked to select the relevant branch. This flow merges the changes from this branch into the "develop" branch (without fast-forwarding, unless branch has only a single commit). 22 | 23 | ## `flow: feature/release/hotfix publish` 24 | 25 | When running this command when an existing feature/release/hotfix branch is checked out, you will asked to confirm publish. Otherwise, you will be asked to select the relevant branch. This flow pushes the target branch to the configured remote. 26 | 27 | ## `flow: feature/release track` 28 | 29 | When running this command you will be prompted to provide a feature name. The command will pull a feature/release from a configured remote and check it out. 30 | 31 | ## `flow: feature pull` 32 | 33 | This will pull a feature from a given remote (not necessarily the configured default remote) and check it out. You will be first prompted to select a remote and then to provide a feature name. 34 | -------------------------------------------------------------------------------- /docs/gitlab.md: -------------------------------------------------------------------------------- 1 | # GitLab integration 2 | 3 | GitSavvy provides some basic integration with GitLab. More features are planned for future versions. At present, both GitLab.com and GitLab enterprise are supported. 4 | 5 | 6 | ## Setup 7 | 8 | When interacting with a public repository, no configuration is required. However, if your repository is private, you will need to add an API key to the GitSavvy configuration. To do so: 9 | 10 | 1. [Create a personal access token.](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#creating-a-personal-access-token); `api` scope should be checked. 11 | 2. After submitting, copy the generated API key. 12 | 3. In the Sublime Menu, open `Preferences > Package Settings > GitSavvy > Settings - User`. 13 | 4. Add your key to the `api_tokens` object (you can find an example in `Preferences > Package Settings > GitSavvy > Settings`). 14 | 15 | 16 | ## Choosing a remote 17 | 18 | By default, GitSavvy will use the URL of `origin` to get its GitLab API URL. If you would like to use a remote other than origin, run `gitlab: set remote for integration` in the command palette. You will be presented with a list of remotes. Once one is selected, this remote will be used for all attempts at integration with GitLab. 19 | 20 | ## `gitlab: review pull request` 21 | 22 | This command will display all open pull requests for the integrated GitLab remote. Once you have made a selection, you can either checkout the pull request as a detached HEAD, checkout the pull request as a local branch, create a branch but not check it out, view the diff of the pull request, or open the pull request in the browser. 23 | -------------------------------------------------------------------------------- /docs/ignoring.md: -------------------------------------------------------------------------------- 1 | # Ignoring changes 2 | 3 | **Note:** None of the following commands are destructive. However, keep in mind that their use may result in unexpected results if specific files are ignored and forgotten. 4 | 5 | 6 | ## `git: ignore current file` 7 | 8 | This command adds an entry for the currently open file to the Git repository's root `.gitignore` file. The command is accessible both through the command palette and through the status dashboard. 9 | 10 | 11 | ## `git: ignore pattern` 12 | 13 | This command adds an entry to the Git repository's root `.gitignore` file. When it is run, you will be prompted for the pattern to add (based on the currently open file). 14 | 15 | 16 | ## `git: assume file unchanged` 17 | 18 | This command instructs Git to temporarily treat the currently open file as if it is unchanged. This may be useful if editing configuration files, etc, without adding an entry to `.gitignore` (which would also show up in the Git status). 19 | 20 | **Note:** These entries are stored in the local `.git` directory and are not tracked in any transparent way. 21 | 22 | ## `git: restore file assumed unchanged` 23 | 24 | This command displays a list of any files for which you've run `git: assume file unchanged`. When selected, the file will no longer be treated as unchanged by Git. 25 | -------------------------------------------------------------------------------- /docs/misc.md: -------------------------------------------------------------------------------- 1 | # Miscellaneous features 2 | 3 | ## `git: init` 4 | 5 | This command will initialize a new repository, or re-initialize a pre-existing one. As such, use it carefully - if GitSavvy detects that Git is already initialized, you will be prompted to confirm. 6 | 7 | When run, you will asked to confirm the root directory of the new Git repository. GitSavvy will attempt to auto-detect this for you. 8 | 9 | This command will also be suggested to you should you attempt to run a GitSavvy command on a file that is not within a valid Git repository. 10 | 11 | ## `git: reset` 12 | 13 | This command will change the HEAD of the current branch to a previous commit. 14 | 15 | When run, you will be asked to select a commit from a list of previous commits. Once you select a commit, you will prompted for a reset mode. Once you select a reset mode the HEAD of the current branch will be changed to the selected commit, and your index and working directory will be updated depending on the reset mode you selected. For more information about reset modes, see the [git reset documentation](https://git-scm.com/docs/git-reset). 16 | 17 | To always use a specific reset mode, set `use_reset_mode` to a valid git reset mode flag (e.g. `--soft`, `--hard`) in `GitSavvy` settings. 18 | 19 | ## `git: reset to branch` 20 | 21 | Like `git: reset`, but it changes the HEAD of the current branch to the HEAD of a selected branch. 22 | 23 | ## `git: reset (reflog)` 24 | 25 | Like `git: reset`, this command will change the HEAD of the current branch to a previous commit, but uses `git reflog` rather than `git log` as the source of available commits. 26 | 27 | ## `git: cherry-pick` 28 | 29 | This command applies a commit from a different branch to the current branch. 30 | 31 | Running the command first prompts for branch selection; then it displays a limited log with commits unique to the chosen branch. Upon selection, the commit is [cherry-picked][1] into the current branch. 32 | 33 | [1]: https://git-scm.com/docs/git-cherry-pick 34 | 35 | ## `git: mv current file` 36 | 37 | Move or rename the current file. The command will prompt for the new filename. 38 | -------------------------------------------------------------------------------- /docs/remotes.md: -------------------------------------------------------------------------------- 1 | # Interacting with remotes 2 | 3 | GitSavvy provides a few mechanisms for interact with remotes. 4 | 5 | ## `git: fetch` 6 | 7 | This updates the local history of remote branches, and downloads all commit objects referenced in that history. If the repository has multiple remotes you will prompted to indicate the remote from which you'd like to fetch, when running this command from the palette. 8 | 9 | ## `git: checkout remote branch as local` 10 | 11 | Assuming you've recently fetched, this command allows you to create a local branch (e.g. `feature-branch-one`) with the same history as a remote branch. Upon running the command, you will be presented with a list of remote branches and, once selected, the local branch will be created and checked out. 12 | 13 | ## `git: pull` 14 | 15 | This command will pull current branch from the tracking branch. If the tracking branch is not set, you will be prompted. If the git config `pull.rebase` is set true, the command will be execulated with `--rebase`. 16 | 17 | 18 | ## `git: pull with rebase` 19 | 20 | Like `git: pull`, but rebasing on the remote branch explictly. 21 | 22 | ## `git: pull from branch` 23 | 24 | When running this command, you will be prompted first for the remote you want to pull from, and then the branch. If your local branch tracks a remote, that branch name will be pre-selected at the second prompt. 25 | 26 | ## `git: pull from branch with rebase` 27 | 28 | Like `git: pull from branch`, but rebasing on the remote branch instead of merging. 29 | 30 | **For the following commands you need to configure a username and password in git, so GitSavvy can use it.** 31 | 32 | ## `git: push` 33 | 34 | This command will push current branch to the tracking branch. If the tracking branch is not set, you will be prompted for a remote and a branch name. 35 | 36 | ## `git: push to branch` 37 | 38 | When running this command, you will be prompted first for the remote you want to push to, and then the branch. 39 | 40 | ## `git: push to branch name` 41 | 42 | When running this command, you will prompted first for the remote you want to push to. Next, you'll be provided a text field to enter the name of the remote branch. This is useful if the remote branch does not yet exist. 43 | -------------------------------------------------------------------------------- /docs/stash.md: -------------------------------------------------------------------------------- 1 | ## Stash 2 | 3 | Command to manipulate stashes. For those commands where you would need to pick a commit it will open a panel to pick which stash to action on. 4 | 5 | ## `git: stash save` 6 | 7 | Create a stash. 8 | 9 | ## `git: stash save including untracked files` 10 | 11 | Create a stash including untracked files. 12 | 13 | ## `git: stash save staged changes only` 14 | 15 | Create a stash from staged changes only. This works by creating a stash of only unstages files. Then creating a stash of all files and pop the first stash(or something in these lines). 16 | 17 | ## `git: stash show` 18 | 19 | Show a stash 20 | 21 | ## `git: stash apply` 22 | 23 | Apply a stash 24 | 25 | ## `git: stash pop` 26 | 27 | Pop a stash 28 | 29 | ## `git: stash drop` 30 | 31 | Discard a stash 32 | -------------------------------------------------------------------------------- /docs/tag_mgmt.md: -------------------------------------------------------------------------------- 1 | # Tag management 2 | 3 | The following commands are provided to manage local and remote tags. 4 | 5 | 6 | ## `git: tags` 7 | 8 | GitSavvy's tag dashboard displays all local and remote tags, and enables you to: 9 | 10 | - create a new tag (`c`) 11 | - select and delete existing tags(s) (`d`) 12 | - select and push tag(s) to a remote (`p`) 13 | - push all tags to a remote (`P`), and 14 | - view the diff commit that is tagged (`l`) 15 | 16 | Remote tags are retrieved asynchronously, and may not display immediately when the view opens. 17 | 18 | 19 | ## `git: quick tag` 20 | 21 | You will be prompted first for a tag name, followed by a tag message. Once entered, a tag will created and associated with the commit at HEAD. 22 | 23 | ## `git: smart tag` 24 | 25 | Similar to `git: quick tag`. A tag name will be automatically generated when the release type is selected. 26 | -------------------------------------------------------------------------------- /docs/testing.md: -------------------------------------------------------------------------------- 1 | # Package Testing 2 | 3 | We try to cover the most crucial functionality with unit tests using 4 | [UnitTesting](https://github.com/randy3k/UnitTesting). To run the tests 5 | locally, you should install UnitTesting via Package Control. 6 | 7 | ## Run the test specs locally 8 | 9 | First you need to [clone](https://github.com/timbrel/GitSavvy#less-simple) GitSavvy repo from source. 10 | Open the directory `GitSavvy` and simply run the command `UnitTesting: Test Current Project` 11 | 12 | ## Some details about DeferrableTestCase 13 | 14 | [DeferrableTestCase][1] is used to write the test cases. They are executed by 15 | the [DeferringTextTestRunner][2] and the runner expects not only regular test 16 | functions, but also generators. If the test function is a generator, it does 17 | the following 18 | 19 | - if the yielded object is a callable, the runner will evaluate the 20 | [callable][3] and check its returned value. If the result is `True`, the 21 | runner continues the generator, if not, the runner will wait until the 22 | condition is met. 23 | 24 | - If the yielded object is an integer, say `x`, then it will [continue][4] the 25 | generator after `x` ms. 26 | 27 | - Otherwise, the `yield` statement will always wait for 10 ms. 28 | 29 | [1]: https://github.com/randy3k/UnitTesting/blob/dc810ee334bb031710b859478faaf50293880995/unittesting/core/st3/runner.py#L49 30 | [2]: https://github.com/randy3k/UnitTesting/blob/dc810ee334bb031710b859478faaf50293880995/unittesting/core/st3/runner.py#L7 31 | [3]: https://github.com/randy3k/UnitTesting/blob/dc810ee334bb031710b859478faaf50293880995/unittesting/core/st3/runner.py#L49 32 | [4]: https://github.com/randy3k/UnitTesting/blob/dc810ee334bb031710b859478faaf50293880995/unittesting/core/st3/runner.py#L57 33 | -------------------------------------------------------------------------------- /git_savvy.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | from .common.commands import * 4 | from .common.ui import * 5 | from .common.global_events import * 6 | from .core.commands import * 7 | from .core.settings import * 8 | from .core.interfaces import * 9 | from .core.runtime import * 10 | from .github.commands import * 11 | from .gitlab.commands import * 12 | 13 | 14 | def plugin_loaded(): 15 | 16 | try: 17 | import package_control.events 18 | except ImportError: 19 | pass 20 | else: 21 | if ( 22 | package_control.events.install('GitSavvy') or 23 | package_control.events.post_upgrade('GitSavvy') 24 | ): 25 | # The "event" (flag) is set for 5 seconds. To not get into a 26 | # reloader excess we wait for that time, so that the next time 27 | # this exact `plugin_loaded` handler runs, the flag is already 28 | # unset. 29 | sublime.set_timeout_async(reload_plugin, 5000) 30 | return 31 | 32 | prepare_gitsavvy() 33 | 34 | 35 | def reload_plugin(): 36 | from .common import util 37 | print("GitSavvy: Reloading plugin after install.") 38 | util.reload.reload_plugin(verbose=False, then=prepare_gitsavvy) 39 | 40 | 41 | def prepare_gitsavvy(): 42 | from .common import util 43 | from .core import runtime 44 | runtime.determine_thread_names() 45 | 46 | # Ensure all interfaces are ready. 47 | sublime.set_timeout_async( 48 | lambda: util.view.refresh_gitsavvy(sublime.active_window().active_view())) 49 | 50 | savvy_settings = sublime.load_settings("GitSavvy.sublime-settings") 51 | if savvy_settings.get("load_additional_codecs"): 52 | sublime.set_timeout_async(reload_codecs) 53 | 54 | 55 | def reload_codecs(): 56 | savvy_settings = sublime.load_settings("GitSavvy.sublime-settings") 57 | fallback_encoding = savvy_settings.get("fallback_encoding") 58 | try: 59 | import imp, codecs, encodings 60 | imp.reload(encodings) 61 | imp.reload(codecs) 62 | codecs.getencoder(fallback_encoding) 63 | except (ImportError, LookupError): 64 | sublime.error_message( 65 | "You have enabled `load_additional_codecs` mode, but the " 66 | "`fallback_encoding` codec cannot load. This probably means " 67 | "you don't have the Codecs33 package installed, or you've " 68 | "entered an unsupported encoding.") 69 | -------------------------------------------------------------------------------- /github/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timbrel/GitSavvy/f0c9067eb061498ba7a07960e5cbaefb414b4994/github/__init__.py -------------------------------------------------------------------------------- /github/commands/__init__.py: -------------------------------------------------------------------------------- 1 | from .open_on_remote import * 2 | from .commit import * 3 | from .configure import * 4 | from .create_fork import * 5 | from .create_repo import * 6 | from .add_fork_as_remote import * 7 | from .pull_request import * 8 | from .open_issue import * 9 | -------------------------------------------------------------------------------- /github/commands/add_fork_as_remote.py: -------------------------------------------------------------------------------- 1 | from itertools import chain 2 | 3 | from ...core.ui_mixins.quick_panel import show_paginated_panel 4 | from .. import github 5 | from .. import git_mixins 6 | from GitSavvy.core.base_commands import GsWindowCommand 7 | from GitSavvy.core.runtime import on_worker 8 | 9 | 10 | __all__ = ( 11 | "gs_github_add_fork_as_remote", 12 | ) 13 | 14 | 15 | class gs_github_add_fork_as_remote(git_mixins.GithubRemotesMixin, GsWindowCommand): 16 | 17 | """ 18 | Get list of repos on GitHub associated with the active repo. Display, and when 19 | selected, add selection as git remote. 20 | """ 21 | 22 | @on_worker 23 | def run(self): 24 | base_remote = github.parse_remote(self.get_integrated_remote_url()) 25 | base_repo_data = github.get_repo_data(base_remote) 26 | parent = None 27 | 28 | forks = [] 29 | if "parent" in base_repo_data: 30 | parent = base_repo_data["parent"] 31 | forks.append(parent) 32 | 33 | if "source" in base_repo_data: 34 | source = base_repo_data["source"] 35 | if parent and parent["clone_url"] != source["clone_url"]: 36 | forks.append(source) 37 | 38 | forks_ = chain(forks, github.get_forks(base_remote)) 39 | show_paginated_panel( 40 | forks_, 41 | self.on_select_fork, 42 | limit=self.savvy_settings.get("github_per_page_max", 100), 43 | format_item=lambda fork: (fork["full_name"], fork), 44 | status_message="Getting forks...") 45 | 46 | def on_select_fork(self, fork): 47 | if not fork: 48 | return 49 | self.fork = fork 50 | self.window.show_quick_panel([fork["clone_url"], fork["ssh_url"]], self.on_select_url) 51 | 52 | def on_select_url(self, index): 53 | if index < 0: 54 | return 55 | elif index == 0: 56 | url = self.fork["clone_url"] 57 | elif index == 1: 58 | url = self.fork["ssh_url"] 59 | 60 | self.window.run_command("gs_remote_add", {"url": url}) 61 | -------------------------------------------------------------------------------- /github/commands/create_fork.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | from ...common import util 4 | from .. import github, git_mixins 5 | from GitSavvy.core.base_commands import GsWindowCommand 6 | from GitSavvy.core.runtime import on_worker 7 | 8 | 9 | START_CREATE_MESSAGE = "Forking {repo} ..." 10 | END_CREATE_MESSAGE = "Fork created successfully." 11 | 12 | 13 | __all__ = ['gs_github_create_fork'] 14 | 15 | 16 | class gs_github_create_fork(GsWindowCommand, git_mixins.GithubRemotesMixin): 17 | 18 | @on_worker 19 | def run(self, default_branch_only=None): 20 | remotes = self.get_remotes() 21 | base_remote_name = self.get_integrated_remote_name(remotes) 22 | base_remote_url = remotes[base_remote_name] 23 | base_remote = github.parse_remote(base_remote_url) 24 | 25 | if default_branch_only is None: 26 | default_branch_only = self.savvy_settings.get("sparse_fork", True) 27 | self.window.status_message(START_CREATE_MESSAGE.format(repo=base_remote.url)) 28 | result = github.create_fork(base_remote, default_branch_only=default_branch_only) 29 | util.debug.add_to_log({"github: fork result": result}) 30 | 31 | url = ( 32 | result["ssh_url"] 33 | if base_remote_url.startswith("git@") 34 | else result["clone_url"] 35 | ) 36 | for remote_name, remote_url in remotes.items(): 37 | if remote_url == url: 38 | sublime.ok_cancel_dialog( 39 | "You forked previously! " 40 | "The fork is available under the name '{}'." 41 | .format(remote_name) 42 | ) 43 | break 44 | else: 45 | self.window.status_message(END_CREATE_MESSAGE) 46 | self.window.run_command("gs_remote_add", { 47 | "url": url, 48 | "set_as_push_default": True 49 | }) 50 | -------------------------------------------------------------------------------- /github/commands/create_repo.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | import os 3 | 4 | from GitSavvy.common import util 5 | from GitSavvy.core.base_commands import GsWindowCommand 6 | from GitSavvy.core.ui_mixins.input_panel import show_single_line_input_panel 7 | from GitSavvy.core.utils import show_panel 8 | from GitSavvy.core.runtime import on_worker 9 | from GitSavvy.github import github 10 | 11 | from GitSavvy.core.base_commands import Args, GsCommand, Kont 12 | 13 | 14 | __all__ = ( 15 | "gs_github_create_repo", 16 | ) 17 | 18 | 19 | def ask_for_repo_name(cmd: GsCommand, args: Args, done: Kont) -> None: 20 | suggestion = ( 21 | os.path.basename(folders[0]) 22 | if (folders := cmd.window.folders()) 23 | else "" 24 | ) 25 | 26 | def on_done(name: str) -> None: 27 | if name: 28 | done(name) 29 | 30 | show_single_line_input_panel("New Repo Name:", suggestion, on_done) 31 | 32 | 33 | def get_github_user_token(cmd: GsCommand, args: Args, done: Kont) -> None: 34 | fqdn = "github.com" 35 | token = cmd.savvy_settings.get("api_tokens", {}).get(fqdn) 36 | if not token: 37 | cmd.window.status_message(f"Abort, no API token found in the settings for {fqdn}.") 38 | return 39 | done(token) 40 | 41 | 42 | class gs_github_create_repo(GsWindowCommand): 43 | defaults = { 44 | "token": get_github_user_token, 45 | "name": ask_for_repo_name 46 | } 47 | 48 | @on_worker 49 | def run(self, token: str, name: str) -> None: 50 | payload = github.create_user_repo(token, name) 51 | self.window.status_message("The repo was created successfully.") 52 | urls = [payload["clone_url"], payload["ssh_url"]] 53 | 54 | def on_remote_name(name: str) -> None: 55 | show_panel(self.window, urls, partial(on_url, name)) 56 | 57 | def on_url(name: str, idx: int) -> None: 58 | url = urls[idx] 59 | self.git("remote", "add", name, url) 60 | self.window.status_message("The new remote was added successfully.") 61 | util.view.refresh_gitsavvy_interfaces(self.window) 62 | 63 | show_single_line_input_panel("Add repo as", "origin", on_remote_name) 64 | -------------------------------------------------------------------------------- /github/commands/open_issue.py: -------------------------------------------------------------------------------- 1 | from webbrowser import open as open_in_browser 2 | 3 | import sublime 4 | from sublime_plugin import EventListener, TextCommand 5 | 6 | from GitSavvy.core.utils import flash 7 | from GitSavvy.core.git_command import GitCommand 8 | from .. import github, git_mixins 9 | 10 | 11 | __all__ = ( 12 | "gs_github_open_issue_at_cursor", 13 | "gs_github_hover_on_issues_controller", 14 | ) 15 | 16 | 17 | ISSUE_SCOPES = "meta.git-savvy.issue-reference" 18 | 19 | 20 | class gs_github_open_issue_at_cursor(TextCommand, git_mixins.GithubRemotesMixin, GitCommand): 21 | def run(self, edit, point=None, open_popup=False): 22 | view = self.view 23 | if point is None: 24 | point = view.sel()[0].begin() 25 | 26 | if not view.match_selector(point, ISSUE_SCOPES): 27 | flash(view, "Not on an issue or pr name.") 28 | return 29 | 30 | def on_navigate(href: str): 31 | open_in_browser(href) 32 | 33 | issue_str = view.substr(view.extract_scope(point)) 34 | url = self.url_for_issue(issue_str) 35 | 36 | if open_popup: 37 | view.show_popup( 38 | '{url}'.format(url=url), 39 | flags=sublime.HIDE_ON_MOUSE_MOVE_AWAY, 40 | location=point, 41 | max_width=1000, 42 | on_navigate=on_navigate 43 | ) 44 | else: 45 | open_in_browser(url) 46 | 47 | def url_for_issue(self, issue_str: str) -> str: 48 | remotes = self.get_remotes() 49 | base_remote_name = self.get_integrated_remote_name(remotes) 50 | base_remote_url = remotes[base_remote_name] 51 | base_remote = github.parse_remote(base_remote_url) 52 | 53 | prefix, issue_nr = issue_str.split('#') 54 | url = f"{base_remote.url}/issues/{issue_nr}" 55 | if prefix: 56 | return url.replace( 57 | f"/{base_remote.owner}/{base_remote.repo}/", 58 | f"/{prefix}/" 59 | ) 60 | return url 61 | 62 | 63 | class gs_github_hover_on_issues_controller(EventListener): 64 | def on_hover(self, view, point, hover_zone): 65 | # type: (sublime.View, int, int) -> None 66 | if ( 67 | hover_zone == sublime.HOVER_TEXT 68 | and view.match_selector(point, ISSUE_SCOPES) 69 | ): 70 | view.run_command("gs_github_open_issue_at_cursor", { 71 | "point": point, 72 | "open_popup": True 73 | }) 74 | -------------------------------------------------------------------------------- /github/git_mixins/__init__.py: -------------------------------------------------------------------------------- 1 | from .remotes import * 2 | -------------------------------------------------------------------------------- /github/git_mixins/remotes.py: -------------------------------------------------------------------------------- 1 | from GitSavvy.core.git_mixins.branches import Upstream 2 | 3 | from typing import Dict, Optional, TYPE_CHECKING 4 | name = str 5 | url = str 6 | 7 | if TYPE_CHECKING: 8 | from GitSavvy.core.git_command import GitCommand 9 | base = GitCommand 10 | else: 11 | base = object 12 | 13 | 14 | NOTSET = "" 15 | UPSTREAM_NOT_SET = Upstream("", "", "", "") 16 | 17 | 18 | class GithubRemotesMixin(base): 19 | def read_gitsavvy_config(self): 20 | # type: () -> Dict[str, str] 21 | return dict( 22 | line[9:].split() 23 | for line in self.git( 24 | "config", 25 | "--get-regex", 26 | r"^gitsavvy\..*", 27 | throw_on_error=False 28 | ).splitlines() 29 | ) 30 | 31 | def get_integrated_branch_name(self): 32 | # type: () -> Optional[str] 33 | return self.read_gitsavvy_config().get("ghbranch") 34 | 35 | def get_integrated_remote_name( 36 | self, 37 | remotes, 38 | current_upstream=UPSTREAM_NOT_SET, 39 | configured_remote_name=NOTSET 40 | ): 41 | # type: (Dict[name, url], Optional[Upstream], Optional[str]) -> name 42 | if len(remotes) == 0: 43 | raise ValueError("GitHub integration will not function when no remotes defined.") 44 | 45 | if len(remotes) == 1: 46 | return list(remotes.keys())[0] 47 | 48 | if configured_remote_name is NOTSET: 49 | configured_remote_name = self.read_gitsavvy_config().get("ghremote") 50 | if configured_remote_name in remotes: 51 | return configured_remote_name 52 | 53 | for name in ("upstream", "origin"): 54 | if name in remotes: 55 | return name 56 | 57 | if current_upstream is UPSTREAM_NOT_SET: 58 | current_upstream = self.get_upstream_for_active_branch() 59 | if current_upstream: 60 | return current_upstream.remote 61 | 62 | raise ValueError("Cannot determine GitHub integrated remote.") 63 | 64 | def get_integrated_remote_url(self): 65 | # type: () -> url 66 | remotes = self.get_remotes() 67 | configured_remote_name = self.get_integrated_remote_name(remotes) 68 | return remotes[configured_remote_name] 69 | 70 | def guess_github_remote(self, remotes): 71 | # type: (Dict[name, url]) -> Optional[name] 72 | if len(remotes) == 1: 73 | return list(remotes.keys())[0] 74 | 75 | upstream = self.get_upstream_for_active_branch() 76 | integrated_remote = self.get_integrated_remote_name(remotes, current_upstream=upstream) 77 | if upstream: 78 | tracked_remote = upstream.remote 79 | if tracked_remote != integrated_remote: 80 | return None 81 | 82 | return integrated_remote 83 | -------------------------------------------------------------------------------- /gitlab/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timbrel/GitSavvy/f0c9067eb061498ba7a07960e5cbaefb414b4994/gitlab/__init__.py -------------------------------------------------------------------------------- /gitlab/commands/__init__.py: -------------------------------------------------------------------------------- 1 | from .open_on_remote import * 2 | # from .commit import * 3 | from .configure import * 4 | # from .create_fork import * 5 | # from .add_fork_as_remote import * 6 | from .merge_request import * 7 | -------------------------------------------------------------------------------- /gitlab/commands/configure.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from sublime_plugin import WindowCommand 3 | 4 | from ..git_mixins import GitLabRemotesMixin 5 | from ...core.git_command import GitCommand 6 | from ...core.ui_mixins.quick_panel import show_branch_panel 7 | 8 | 9 | __all__ = ( 10 | "gs_gitlab_configure_remote", 11 | ) 12 | 13 | 14 | class gs_gitlab_configure_remote(WindowCommand, GitLabRemotesMixin, GitCommand): 15 | 16 | def run(self): 17 | sublime.set_timeout_async(self.run_async) 18 | 19 | def run_async(self): 20 | show_branch_panel( 21 | self.on_branch_selection, 22 | ask_remote_first=True, 23 | selected_branch=self.get_integrated_branch_name() 24 | ) 25 | 26 | def on_branch_selection(self, branch): 27 | """ 28 | After the user selects a branch, configure integrated remote branch. 29 | """ 30 | remote, remote_branch = branch.split("/", 1) 31 | 32 | self.git("config", "--local", "--unset-all", "GitSavvy.glRemote", throw_on_error=False) 33 | self.git("config", "--local", "--add", "GitSavvy.glRemote", remote) 34 | 35 | self.git("config", "--local", "--unset-all", "GitSavvy.glBranch", throw_on_error=False) 36 | self.git("config", "--local", "--add", "GitSavvy.glBranch", remote_branch) 37 | 38 | sublime.status_message("Successfully configured GitLab integration.") 39 | -------------------------------------------------------------------------------- /gitlab/git_mixins/__init__.py: -------------------------------------------------------------------------------- 1 | from .remotes import * 2 | -------------------------------------------------------------------------------- /gitlab/git_mixins/remotes.py: -------------------------------------------------------------------------------- 1 | from GitSavvy.core.git_mixins.branches import Upstream 2 | 3 | 4 | from typing import Dict, Optional, TYPE_CHECKING 5 | name = str 6 | url = str 7 | 8 | if TYPE_CHECKING: 9 | from GitSavvy.core.git_command import GitCommand 10 | base = GitCommand 11 | else: 12 | base = object 13 | 14 | 15 | NOTSET = "" 16 | UPSTREAM_NOT_SET = Upstream("", "", "", "") 17 | 18 | 19 | class GitLabRemotesMixin(base): 20 | def read_gitsavvy_config(self): 21 | # type: () -> Dict[str, str] 22 | return dict( 23 | line[9:].split() 24 | for line in self.git( 25 | "config", 26 | "--get-regex", 27 | r"^gitsavvy\..*", 28 | throw_on_error=False 29 | ).splitlines() 30 | ) 31 | 32 | def get_integrated_branch_name(self): 33 | # type: () -> Optional[str] 34 | return self.read_gitsavvy_config().get("glbranch") 35 | 36 | def get_integrated_remote_name( 37 | self, 38 | remotes, 39 | current_upstream=UPSTREAM_NOT_SET, 40 | configured_remote_name=NOTSET 41 | ): 42 | # type: (Dict[name, url], Optional[Upstream], Optional[str]) -> name 43 | if len(remotes) == 0: 44 | raise ValueError("GitLab integration will not function when no remotes defined.") 45 | 46 | if len(remotes) == 1: 47 | return list(remotes.keys())[0] 48 | 49 | if configured_remote_name is NOTSET: 50 | configured_remote_name = self.read_gitsavvy_config().get("glremote") 51 | if configured_remote_name in remotes: 52 | return configured_remote_name 53 | 54 | for name in ("upstream", "origin"): 55 | if name in remotes: 56 | return name 57 | 58 | if current_upstream is UPSTREAM_NOT_SET: 59 | current_upstream = self.get_upstream_for_active_branch() 60 | if current_upstream: 61 | return current_upstream.remote 62 | 63 | raise ValueError("Cannot determine GitLab integrated remote.") 64 | 65 | def get_integrated_remote_url(self): 66 | # type: () -> url 67 | remotes = self.get_remotes() 68 | configured_remote_name = self.get_integrated_remote_name(remotes) 69 | return remotes[configured_remote_name] 70 | 71 | def guess_gitlab_remote(self, remotes): 72 | # type: (Dict[name, url]) -> Optional[name] 73 | if len(remotes) == 1: 74 | return list(remotes.keys())[0] 75 | 76 | upstream = self.get_upstream_for_active_branch() 77 | integrated_remote = self.get_integrated_remote_name(remotes, current_upstream=upstream) 78 | if upstream: 79 | tracked_remote = upstream.remote 80 | if tracked_remote != integrated_remote: 81 | return None 82 | 83 | return integrated_remote 84 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "install": "messages/install.txt", 3 | "1.1.0": "messages/1.1.0.txt", 4 | "1.2.0": "messages/1.2.0.txt", 5 | "1.3.1": "messages/1.3.1.txt", 6 | "1.4.1": "messages/1.4.0.txt", 7 | "2.0.0": "messages/2.0.0.txt", 8 | "2.1.0": "messages/2.1.0.txt", 9 | "2.2.0": "messages/2.2.0.txt", 10 | "2.3.0": "messages/2.3.0.txt", 11 | "2.4.0": "messages/2.4.0.txt", 12 | "2.5.0": "messages/2.5.0.txt", 13 | "2.6.0": "messages/2.6.0.txt", 14 | "2.7.0": "messages/2.7.0.txt", 15 | "2.8.0": "messages/2.8.0.txt", 16 | "2.9.0": "messages/2.9.0.txt", 17 | "2.10.0": "messages/2.10.0.txt", 18 | "2.11.0": "messages/2.11.0.txt", 19 | "2.12.0": "messages/2.12.0.txt", 20 | "2.12.1": "messages/2.12.1.txt", 21 | "2.13.0": "messages/2.13.0.txt", 22 | "2.14.0": "messages/2.14.0.txt", 23 | "2.14.1": "messages/2.14.1.txt", 24 | "2.14.2": "messages/2.14.2.txt", 25 | "2.16.2": "messages/2.16.2.txt", 26 | "2.16.4": "messages/2.16.4.txt", 27 | "2.16.5": "messages/2.16.5.txt", 28 | "2.16.6": "messages/2.16.6.txt", 29 | "2.16.7": "messages/2.16.7.txt", 30 | "2.17.0": "messages/2.17.0.txt", 31 | "2.17.1": "messages/2.17.1.txt", 32 | "2.17.3": "messages/2.17.3.txt", 33 | "2.17.4": "messages/2.17.4.txt", 34 | "2.18.0": "messages/2.18.0.txt", 35 | "2.19.0": "messages/2.19.0.txt", 36 | "2.20.0": "messages/2.20.0.txt", 37 | "2.21.0": "messages/2.21.0.txt", 38 | "2.22.0": "messages/2.22.0.txt", 39 | "2.23.0": "messages/2.23.0.txt", 40 | "2.25.0": "messages/2.25.0.txt", 41 | "2.26.0": "messages/2.26.0.txt", 42 | "2.27.0": "messages/2.27.0.txt", 43 | "2.28.0": "messages/2.28.0.txt", 44 | "2.30.0": "messages/2.30.0.txt", 45 | "2.31.0": "messages/2.31.0.txt", 46 | "2.34.0": "messages/2.34.0.txt", 47 | "2.36.0": "messages/2.36.0.txt", 48 | "2.38.0": "messages/2.38.0.txt", 49 | "2.39.0": "messages/2.39.0.txt", 50 | "2.41.0": "messages/2.41.0.txt", 51 | "2.42.0": "messages/2.42.0.txt" 52 | } 53 | -------------------------------------------------------------------------------- /messages/1.1.0.txt: -------------------------------------------------------------------------------- 1 | Changes since 1.0.0: 2 | 3 | Features: 4 | - Always enable panel output for specified commands with `show_panel_for`. 5 | - Record input/output of git commands and make available as JSON. 6 | - `git: generate changelog` based on commit history since ref. 7 | - Add configurable colors for inline-diff view. 8 | - Add an option to show a diff in the commit view. 9 | - Add a keybinding to the commit view to easily sign off commits. 10 | 11 | UX: 12 | - Prompt user when making destructive changes (e.g. discarding). 13 | 14 | Bugs: 15 | - Avoid empty sublime.Region for removed newline. 16 | - Fix forward slash ignoring on branch names. 17 | - Correct GitHub URL for users who copy/paste address out of browser (without `.git`) 18 | 19 | Other: 20 | - README updates. 21 | 22 | Contributors: 23 | - Oliver Hoffmann 24 | - Dale Bustad 25 | - Ethan Bustad 26 | - Philipp Klose 27 | - Harel 28 | - Adrian L Lange 29 | -------------------------------------------------------------------------------- /messages/1.2.0.txt: -------------------------------------------------------------------------------- 1 | Changes since 1.1.0: 2 | 3 | Feature: 4 | - Show Git status in ST3 status bar (can be disabled in settings file). 5 | - Add `git: push to remote branch name` command to the palette. 6 | 7 | Enhancement: 8 | - Correct branch pattern matching when generating status dashboard. 9 | - Add Undo feature to inline-diff view. Useful if you mistakenly blow away a hunk. 10 | - Disable Vintageous in status view. 11 | - Add HEAD commit hash to status view. 12 | - Add an option to suppress the command line args when outputting to a panel. 13 | 14 | Fix: 15 | - Inline-diff would not display/behave correctly for files not ending in newline. 16 | - Git blame failed for files with boundary commits. 17 | - `git: push to branch` would fail when remote branch doesn't exist. 18 | - Fetching would fail when more than one remote. 19 | - Correct protection for destructive actions. 20 | 21 | Contributors: 22 | - Ethan Bustad 23 | - Dale Bustad 24 | - Adrian L Lange 25 | - Jeff Langston 26 | -------------------------------------------------------------------------------- /messages/1.3.1.txt: -------------------------------------------------------------------------------- 1 | Changes since 1.2.0: 2 | 3 | Features: 4 | - New `git: branch` dashboard. 5 | - New `git: tags` dashboard. 6 | - New `git: checkout current file` palette command. 7 | - New `git: push (force)` command palette option. 8 | 9 | Under the hood: 10 | - Resumable UI interfaces with easy listener registration. 11 | 12 | Enhancements: 13 | - Customized Git arguments for `git: graph` palette command. 14 | - `git: graph` view shows remote branchs, has syntax highlighting, and can checkout and view commit. 15 | - Enable definition of global flags for specific Git commands. 16 | 17 | Fix: 18 | - Inline-diffs would fail to apply in certain cases in Windows. 19 | - Syntax-highlighting for branch names in status and branch dashboard views. 20 | - Parse branch output properly if local branch tracks remote but not ahead/behind. 21 | - Allow numbers in names for remote. 22 | - Display `git: log` quick panel before keypress in Windows. 23 | - Create cursor at pos 0 in stash view, not in status view. 24 | - Annoying error when opening new window, due to status bar changes. 25 | 26 | Contributors: 27 | - Adrian L Lange 28 | - Nicolás Santángelo 29 | - Dale Bustad 30 | - Laas Toom 31 | -------------------------------------------------------------------------------- /messages/1.4.0.txt: -------------------------------------------------------------------------------- 1 | Lots of changes recently, thanks to eveyone who has contributed and to those who have reported and discussed issues! 2 | 3 | Changes since 1.3.0: 4 | 5 | Enhancement: 6 | - Last-used remote for push, pull, and fetch palette commands will be pre-selected. 7 | - User can now select which remote to use for GitHub integration. 8 | - Inline-diff view will highlight words that have changed. 9 | - GitSavvy will offer to set tracking branch on `git: push` when unconfigured. 10 | - Status view can now discard untracked files. 11 | - Re-use existing interface if present. 12 | - Remote refs will be pruned on fetch. 13 | - Fetch added to branch dashboard. 14 | - Optionally scroll to first change in inline-diff view. 15 | - Now possible to commit from quick stage panel. 16 | 17 | Refactor: 18 | - GitSavvy syntax-files no longer show in command palette. 19 | - When commiting including unstaged files, use `git commit -a`. 20 | - Port status dashboard to new UI Interface paradigm. 21 | - Minutia. 22 | 23 | Fix: 24 | - Only offer init once for view with missing `.git` directory. 25 | - Cursor position was inconsistent when interfaces would re-render. 26 | - Fragile handling of repo_path in edge conditions. 27 | - Ensure interfaces are ready when plugins finish loading. 28 | - Error would display when file opened at non-real path. 29 | - Incorrect behavior when deleting remote branches from branch dashboard. 30 | - RegEx for detecting sections in blame view worked but too permissive. 31 | - GitHub issue integration would not work in some environments. 32 | - Debug reload would break interface refresh. 33 | 34 | Documentation: 35 | - Add Codacy badge to README. 36 | - Add branch/tag dashboard to README. 37 | - Show correct contextual help for diff view. 38 | - Update README with new highlights. 39 | 40 | Contributors: 41 | - Nicolás Santángelo 42 | - Josh Goebel 43 | - Dale Bustad 44 | -------------------------------------------------------------------------------- /messages/2.0.0.txt: -------------------------------------------------------------------------------- 1 | # GitSavvy 2.0 2 | 3 | Things have been moving a bit more slowly with GitSavvy lately, but 2.0 brings 4 | a significant new feature: the **rebase dashboard**. 5 | 6 | The dashboard will show all commits made since the last common ancestor. You 7 | can take action on those commits, including the ability to re-order, edit 8 | commit messages, and squash. Once you've started an actual rebase (this is 9 | distinguished from the other actions in the dashboard), any conflicts will 10 | be displayed and you'll have options for resolving. 11 | 12 | If you've used `git rebase --interactive` from the terminal, there may be one surprise in that all actions take _immediate_ effect. However, the dashboard 13 | also supports unlimited Undo/Redo. The goal is to make rebase accessible for 14 | users that aren't familiar - if you have any UX feedback, please open an issue. 15 | 16 | Changes since 1.4.1: 17 | 18 | Features: 19 | - Rebase dashboard, including re-ordering, squashing, message editing, conflict resolution, and undo/redo. 20 | 21 | Enhancement: 22 | - Add option to append customized message to the default commit help. 23 | 24 | UX: 25 | - Move unstaged and untracked sections to top of status dashboard. 26 | - Don't include `--no-columns` flag on `git branch`. 27 | - Add space in `git: merge` palette between branch name and tracking info. 28 | 29 | Bug: 30 | - Native line-ending was used on Windows when autocrlf is false. 31 | - When staging directory, ignored files were also staged. 32 | - Status dashboard would not display after `git init`. 33 | - Update key-bindings on Linux to use CTRL-* instead of SUPER-*. 34 | - Status dashboard failed to render; stash list didn't include "WIP on branchname". 35 | 36 | Contributors: 37 | - Dale Bustad 38 | - old9 39 | -------------------------------------------------------------------------------- /messages/2.1.0.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.0.0: 2 | 3 | Feature: 4 | - Easily add GitHub fork as local remote. 5 | 6 | Enhancement: 7 | - Checkout remote branch as local in branch dashboard. 8 | - Support ignoring EOL whitespace in inline diff view. 9 | 10 | Bug: 11 | - Rebase edit would fail when content contained format key. 12 | - Branch names with dots would not always be recognized. 13 | 14 | Contributors: 15 | - Dale Bustad 16 | - David Devlin 17 | - Pavel Savchenko 18 | -------------------------------------------------------------------------------- /messages/2.10.0.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.9.1: 2 | 3 | Feature: 4 | - Log by author prompt with a list of committers 5 | - Use `Goto Symbol` to navigate in all views 6 | - Add `git add --edit` equivalent. 7 | - Add and remove remotes 8 | - resolve conflict (stage merged file) on successful merge 9 | - allow prompt to specify custom command argument 10 | - Use sublime new syntax file 11 | 12 | Enhancement: 13 | - Squash in rebase dashboard now behaves like `git rebase -i`. 14 | - Move cursor in rebase dashboard when moving commits up/down. 15 | - Rebase dashboard actions only effect selected commit and those that follow. 16 | - Drop commit from rebase dashboard. 17 | 18 | Fix: 19 | - Launching merge tool would fail for non-ASCII files. 20 | - Could not squash into first commit after squash direction change. 21 | - plugin_host would crash when navigating past end of graph view 22 | - When amending, prepopulated commit message would include two extra spaces. 23 | - When amending with show_commit_diff enabled, unstaged changes were displayed. 24 | - Update language definition for non-trailing spaces 25 | - Set tab size for dashboards to enable code folding for sections. 26 | - Allow user to disable display of branch descriptions. 27 | 28 | Improve: 29 | - Blame syntax 30 | - Graph syntax 31 | - Diff syntax 32 | - Inline diff syntax 33 | - Update blame syntax 34 | - Make_commit syntax 35 | - Diff syntax 36 | - Show_commit syntax 37 | - Graph syntax 38 | - Tags syntax 39 | - Rebase syntax 40 | - Branch syntax 41 | - Status syntax 42 | 43 | Contributors: 44 | - David Devlin 45 | - Felix 46 | - Pavel Savchenko 47 | - Dave Nicolson 48 | - gwenzek 49 | - Simon 50 | - Randy Lai 51 | - Dale Bustad 52 | -------------------------------------------------------------------------------- /messages/2.11.0.txt: -------------------------------------------------------------------------------- 1 | Hey everybody, 2 | 3 | There are a lot of changes and refinements in this release. Remember to reach out to us on Gitter[0] or open an issue[1] if you run into any problems, and thanks for using GitSavvy! 4 | 5 | [0]: https://gitter.im/divmain/GitSavvy 6 | [1]: https://github.com/divmain/GitSavvy/issues 7 | 8 | 9 | ### Changes since 2.10.0 ### 10 | 11 | Feature: 12 | - Cherry-pick from Branch Commit Comparison view. 13 | - New commands to add and remove remotes. 14 | 15 | Enhancement: 16 | - Inline-diff and diff views are now re-used for the same file. 17 | - GitSavvy color schemes no longer appear in Sublime's color scheme menu. 18 | - Help menus can now be hidden in dashboards. 19 | - After fetching, user is now prompted to fetch the remote. 20 | - Branch view now support simultaneous merging from multiple branches. 21 | - Lots of improvements to GitSavvy internal syntaxes and dashboard UIs. 22 | - Tags in tag dashboard are now sorted with semver. 23 | - Improve experience when using Sublime as editor for terminal-based interactive rebase. 24 | - `git: blame` and `git: log` quick panels now remember previously selected option. 25 | - Show keyboard shortcuts for graph view in a popup. 26 | - Users can now augment the execution environment of custom Git commands. 27 | - Add easy access to GitSavvy settings. 28 | - Navigate with . and , in the Rebase dashboard, plus 29 | - Navigate with . and , in the Tag dashboard. 30 | - Dashboards also include Vintageous-friendly navigation key-bindings. 31 | - Index syntax files asynchronously, to avoid blocking Sublime startup. 32 | - Default to selecting the active remove in quick panels. 33 | - Prompt for remote tracking branch when opening a GitHub pull request. 34 | 35 | Fix: 36 | - Rebase dashboard actions would be allowed with working directory in unclean state. 37 | - Ignoring files would result in "# added by GitSavvy" added to `.gitignore` file. 38 | - Log view commands were not functioning correctly. 39 | - Status dashboard key-bindings did not work for files starting with `.` 40 | - User was unable to view tag commit when remotes were not displayed in tags dashboard. 41 | - Syntax highlighting failed in dashboard for SHA1 refs longer than 7 characters. 42 | - GitSavvy would fail to load on some platforms due to `yaml` missing from standard library. 43 | 44 | Internal: 45 | - Revamped debug reload using import hooks. 46 | 47 | 48 | ### Contributors ### 49 | 50 | - David Nicolson 51 | - Dale Bustad 52 | - Petr Marek 53 | - Eldar Abusalimov 54 | - laggingreflex 55 | - Simon 56 | - Shadab Zafar 57 | - Allen Bargi 58 | - Sindri Guðmundsson 59 | - Pavel Savchenko 60 | - Randy Lai 61 | - MJ 62 | -------------------------------------------------------------------------------- /messages/2.12.0.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.11.0: 2 | 3 | Enhancement: 4 | - Add a smart-tag key-binding to the Tags dashboard. 5 | - Don't prompt for a remote when there is only one. 6 | 7 | Fix: 8 | - Clean up refresh and output in status-bar for custom Git commands. 9 | - Caching of pre-rebase state was broken. 10 | - Tag dashboard commands would fail due to command scope context mismatch. 11 | 12 | Internal: 13 | - Reorganize active-branch related code. 14 | - Update contribution guidelines for commit subject structure. 15 | - Add debug tracing and fancy output for GitSavvy debug reloads. 16 | 17 | Contributors: 18 | - Randy Lai 19 | - Dale Bustad 20 | - Eldar Abusalimov 21 | -------------------------------------------------------------------------------- /messages/2.14.0.txt: -------------------------------------------------------------------------------- 1 | I want to give a big thank-you to Pavel, Randy, and Simon for all of their work on GitSavvy. I've been busy on other projects, and they've been managing PRs and moving things forward significantly. 2 | 3 | Here are the changes since 2.13.0: 4 | 5 | New feature: 6 | - `git: show current file at commit` 7 | - Proper `git: clone` support. 8 | 9 | Enhancement: 10 | - Improved keyboard navigation in dashboards. 11 | - Improved git-log functionality. 12 | - Enable preservation of merges while rebasing. 13 | - New shortcuts in the Bransh dashboard. 14 | - Add pagination to various palette commands. 15 | - Better status information when rebasing. 16 | - Include merge hash in changelog generation. 17 | - `git: push` asynchronously. 18 | - Status updates when pulling/pushing. 19 | 20 | Bug fixes: 21 | - Sidebar refresh tweaks. 22 | 23 | And several small tweaks, bug fixes, and internal refactors! 24 | 25 | Contributors: 26 | - Simon 27 | - Petr Marek 28 | - Randy Lai 29 | - Pavel Savchenko 30 | -------------------------------------------------------------------------------- /messages/2.14.1.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.14.0: 2 | 3 | Add: 4 | - Frequency limit on update status bar 5 | - Docs for `Signing your commits with GPG` 6 | - Support emoji in branch name 7 | - Allow user to stash only changes that have been staged. 8 | 9 | Enhancement: 10 | - Add setting to custom command to open in new buffer 11 | - Add setting to custom command to open with specific syntax 12 | 13 | Fix: 14 | - Encoding problems in inline diff 15 | - Clone doesn't need to be executed from a git repo 16 | - Trigger stash command outside of stash section 17 | - Dropping a stash is destructive, and will prompt before executed 18 | - support parsing "git://" remote URI to Github URL 19 | 20 | Contributors: 21 | - Simon 22 | - Randy Lai 23 | - Dale Bustad 24 | - Pavel Savchenko 25 | -------------------------------------------------------------------------------- /messages/2.14.2.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.14.1: 2 | 3 | Fix: 4 | - Show open inline diff if it already open form status dashboard 5 | - Push tags command is not blocking 6 | - added self.get_short_hash to honor core.abbrev settings (#642) 7 | 8 | Other: 9 | - Sort branches by recent commited (#632) 10 | - Check that file exist before opening (#648) 11 | 12 | Improvement: 13 | - Remove some clutter from debug.log (#635) 14 | - Warn the user when we have permission error (#636) 15 | - it is not necessary to check if HEAD is rebased 16 | - use message_dislog for show the messages 17 | - do not allow squashing merges 18 | 19 | Contributors: 20 | - Simon 21 | - Juan Pumarino 22 | - Pavel Savchenko 23 | - Vlad Tsepelev 24 | - Randy Lai 25 | -------------------------------------------------------------------------------- /messages/2.16.4.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.16.2: 2 | 3 | Enhancement: 4 | - don't show "following output" where stderr is empty 5 | - select the branch name while creating a new branch 6 | 7 | Internal: 8 | - add UnitTesting build system 9 | - maintainerd - don't enforce empty line 10 | - add ISSUE_TEMPLATE.md and PULL_REQUEST_TEMPLATE.md 11 | 12 | Fix: 13 | - typo in some keymaps: control -> ctrl 14 | 15 | Feature: 16 | - `github: create fork` to create fork on github 17 | - git stash in command panel 18 | - better selection of base ref branch 19 | 20 | Contributors: 21 | - Randy Lai 22 | - Simon 23 | - Pavel Savchenko 24 | -------------------------------------------------------------------------------- /messages/2.16.5.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.16.4: 2 | 3 | Fix: 4 | - various small bug fixes 5 | 6 | Enhancement: 7 | - Add some default syntax specific settings 8 | - Cache settings object for multi get/set operations 9 | - Super+, open settings for each of the different syntaxes 10 | - refactor blame command 11 | - Improvements to fetch quick panel 12 | 13 | Internal: 14 | - Add unittest for JSON resources 15 | 16 | Contributors: 17 | - DeathAxe 18 | - Randy Lai 19 | - Simon 20 | - David Devlin 21 | -------------------------------------------------------------------------------- /messages/2.16.6.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.16.5: 2 | 3 | New Feature: 4 | - Add mv command. 5 | 6 | Fix: 7 | - various small fixes 8 | - Use correct argument for blame all commits 9 | - fix ignoring file functionality 10 | - don't show "OS error" in rebase view 11 | - Commit view respect the include_unstaged 12 | 13 | Enhancement: 14 | - set the default for max_items_in_tags_dashboard to -1 15 | - select commit as base in rebase dashboard 16 | 17 | Contributors: 18 | - Randy Lai 19 | - David Arnold 20 | - Eric Huss 21 | - David Devlin 22 | - Simon 23 | -------------------------------------------------------------------------------- /messages/2.16.7.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.16.6: 2 | 3 | Feature: 4 | - quick stage and amend 5 | - git remote rename 6 | 7 | Enhancement: 8 | - show diff stat when `show_commit_diff` is "full". 9 | - show url when removing remote 10 | - manipulate commits with untracked files 11 | - reset to original commit after failure 12 | - fixup should be safe for untracked files 13 | - use ssh url instead of url when adding fork 14 | - support .sublime-color-scheme in inline diff view 15 | - git pull from remote tracking branch directly 16 | - refactor pull and pull with rebase 17 | 18 | Fix: 19 | - only show diff patch when `show_commit_diff` is "full" 20 | 21 | Contributors: 22 | - Randy Lai 23 | -------------------------------------------------------------------------------- /messages/2.17.0.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.16.6: 2 | 3 | Requirement: 4 | - bump git requirement, git v1.19.0 or above is required 5 | 6 | Feature: 7 | - quick stage and amend 8 | - git remote rename 9 | - implement GitLab merge request review 10 | - Pedantic commit messages 11 | - add helpers to handle merge conflicts in status dashboard 12 | - Live output: Show STDIN when present in live log 13 | 14 | Enhancement: 15 | - refreash UI when merge failed 16 | - add `abort rebase` command 17 | - Add a refresh_gitsavvy_interfaces PoC for bug #835 18 | - show diff stat when `show_commit_diff` is "full". 19 | - manipulate commits with untracked files 20 | - reset to original commit after failure 21 | - use ssh url instead of url when adding fork 22 | - support .sublime-color-scheme in inline diff view 23 | - git pull from remote tracking branch directly 24 | - use GsPullBase mixin 25 | - add pull with rebase 26 | - do commit synchronously when commit_on_close is true 27 | - make following file renames optional 28 | - rename `git: add (edit)` command 29 | 30 | Fix: 31 | - only show diff patch when `show_commit_diff` is "full" 32 | - use unicode to write .gitignore 33 | - hash tag sign is a valid char for branch name 34 | - load user specfic theme first 35 | 36 | Internal: 37 | - Use window.status_message instead of sublime.status_message 38 | 39 | Docs: 40 | - Add doc for setting PATH environment 41 | 42 | Contributors: 43 | - Guillaume Wenzek 44 | - Simon 45 | - Pavel Savchenko 46 | - Tom van Ommeren 47 | - Randy Lai 48 | -------------------------------------------------------------------------------- /messages/2.17.1.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.17.0: 2 | 3 | Enhancement: 4 | - use a more meanful branch name when reviewing PRs 5 | 6 | Fix: 7 | - fix show file bug on Windows 8 | - fix tag dashboard bug if hash length != 8 9 | - Pass startupinfo to `subprocess.check_output` on Windows 10 | 11 | Contributors: 12 | - herr kaste 13 | - Randy Lai 14 | -------------------------------------------------------------------------------- /messages/2.17.3.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.17.2: 2 | 3 | Enhancement: 4 | - support prefix in prerelease when using smart tag 5 | - support project-wise settings 6 | - make `rebase_default_base_ref` more consistant with project-wise settings 7 | - allow to use "git: blame current file" at blame view 8 | - Opening commit from commadline respect setting 9 | 10 | Feature: 11 | - git: checkout current file at commit 12 | 13 | Fix: 14 | - Pedantic commit checks allows comment on 2. line 15 | - On startup in-memory `instances` cache can be empty 16 | - fix set status on cherry-pick done 17 | 18 | Other: 19 | - Emphasize in docs when to use run_in_thread 20 | - Fix subprocess command crashing with OSError 21 | - Include A traceback when an exception happens and we don't handle it 22 | 23 | 24 | Contributors: 25 | - herr kaste 26 | - Simon 27 | - Randy Lai 28 | - Pavel Savchenko 29 | - joan 30 | - Maarten Nieber 31 | -------------------------------------------------------------------------------- /messages/2.17.4.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.17.3: 2 | 3 | Feature: 4 | - git push --force-with-lease 5 | 6 | Internal: 7 | - Refactor away show_commit_info from LogPanel (Merge 4dd2091) 8 | - LogPanel, disable highlight if on_highlight is't callable (Merge 4dd2091) 9 | 10 | Enhancement: 11 | - Move the project GitSavvy key into "settings" 12 | - Show reverse diff from commit to workspace for checking out files 13 | - expand home dir when cloning a project 14 | - refactor show_input_panel and disable modifier+enter keys in single line input panel 15 | - expand home dir when cloning a project 16 | 17 | Fix: 18 | - expose on_highlight to LogMixin 19 | - do not continue when the window is not a project 20 | - use fallback encoding to decode stderr 21 | - push selected non-tracking branch 22 | 23 | Contributors: 24 | - Pavel Savchenko 25 | - herr kaste 26 | - Simon 27 | - Randy Lai 28 | -------------------------------------------------------------------------------- /messages/2.18.0.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.17.6: 2 | 3 | Fix: 4 | - update status bar when the view is a widget 5 | - make sure project settings are loaded properly 6 | - fix a bug related to prompt_on_abort_commit 7 | - 'checkout file at commit' shows commit panel 8 | - sanitize ansi sequence 9 | - Fix theme generator for sublime-color-schemes 10 | - avoid splitlines in blame 11 | 12 | Enhancement: 13 | - Optimize status interface 14 | - Optimize log graph interface 15 | - improve diff view 16 | - rename `git: tags` to `git: tag` 17 | - honor prepare_commit_msg and pre_commit hooks 18 | - offer initialization for repo_path 19 | - Add more complex git output scopes to diff syntax 20 | - Add support for "env" settings and GIT_OPTIONAL_LOCKS 21 | 22 | Other: 23 | - Show toggle remotes help message only if there are any remotes (#1047) 24 | - Include flake8 config for convenience 25 | - Tag the line containing HEAD for styling purpopses (#1051) 26 | - Add test cases for log graph view not showing the panel initially (#1068) 27 | - Refresh graph view after checkout 28 | 29 | Feature: 30 | - Add GPG support via a wrapper 31 | 32 | 33 | Contributors: 34 | - Randy Lai 35 | - Simon 36 | - DeathAxe 37 | - herr kaste 38 | - luv 39 | - Pavel Savchenko 40 | -------------------------------------------------------------------------------- /messages/2.19.0.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.18.0: 2 | 3 | Features: 4 | Colorize the git graph 5 | Show references to commit in log panel 6 | Diff support increasing and reducing context lines, open a diff, click `+` or `-`, or `?` to get help 7 | Use tab to flip base and target in a diff view 8 | Improve the jumping from diff to file accuracy 9 | Diffing the same file twice only open one view 10 | 11 | Improvements 12 | Add `git: [graph|log] current branch` to command panel 13 | Remember last checked out branch so it can suggest that branch next time to checkout 14 | Speedup inline diff refreshing 15 | Don't block inline diff on huge files 16 | Always focus the interface when you open it 17 | When navigating ensure scroll is all the way left 18 | Don't propose to merge with self 19 | Fix checkout multiple files 20 | Stop leaking log 21 | Opening inline diff takes an argument to open inline diff on the same location, for more info look at #1137 22 | 23 | Internal 24 | Use flake8 25 | Simplify log commands 26 | When logging is enabled, log to console too 27 | Use safe_load to parse yaml 28 | Update reloader 29 | MyPy setup 30 | 31 | Contributors: 32 | - herr kaste 33 | - Simon 34 | - Pavel Savchenko 35 | - Luis Puerto 36 | - DeathAxe 37 | -------------------------------------------------------------------------------- /messages/2.2.0.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.1.0: 2 | 3 | Feature: 4 | - After selecting a commit from the `git: log` panel, you can now show the commit, compare that commit against your working directory, or compare that commit against the git index. 5 | 6 | Enhancement: 7 | - Navigation inside `git: graph` view. 8 | - Quick panel inside `git: graph` view, displaying extra commit info. 9 | - Add `git: graph all branches` command. 10 | - Make default comparison configurable in Rebase dashboard. 11 | - Validate branch name when creating. 12 | 13 | Bug: 14 | - `git push --force` would fail when no upstream set but upstream exists. 15 | - `super_key` was not set to `CTRL` on Linux 16 | - Branch status would not display correctly when remote tracking branch disappeared. 17 | - The inclusion of global Git flags would cause exclusion of dynamic args. 18 | 19 | Contributors: 20 | - Simon 21 | - Dale Bustad 22 | - Max Mykhailenko 23 | - David Devlin 24 | -------------------------------------------------------------------------------- /messages/2.20.0.txt: -------------------------------------------------------------------------------- 1 | GitSavvy 2.20.0 2 | =============== 3 | 4 | - GitSavvy moved to https://github.com/timbrel/GitSavvy 5 | 6 | - Improve parsing and colorization of diffs in "word-diff" mode. Use 7 | `` to enter that mode as usual. 8 | 9 | - Improve the stash view. 10 | Enter the stash view e.g. from the status dashboard via ``. While 11 | looking at the stash try `` for help, or just `` to see the 12 | available, typical actions. 13 | 14 | - Renovate the standard status view to fix a lot of small edge cases. Esp. 15 | improve the cursor jumping while staging/unstaging etc. 16 | 17 | - Fix newline handling on Windows when .gitignoring files. (@joreiff) 18 | 19 | We now ship code to reload the plugin after updates automatically and silently. 20 | This will be in full effect for the very next update you see. 21 | 22 | 23 | For more info: 24 | https://github.com/timbrel/GitSavvy/compare/2.19.1...2.20.0 25 | -------------------------------------------------------------------------------- /messages/2.21.0.txt: -------------------------------------------------------------------------------- 1 | GitSavvy 2.21.0 2 | =============== 3 | 4 | - We now highlight intra-line differences when we show git-diffs. 5 | https://github.com/timbrel/GitSavvy/pull/1220 6 | 7 | For the annonations we use the scopes "diff.inserted.char.git-savvy.diff" 8 | and "diff.deleted.char.git-savvy.diff". These are the same scopes 9 | Sublime uses for their "Incremental Diff" feature (but namespaced with 10 | ".git-savvy.diff") so every user should see proper colors right 11 | away. 12 | 13 | 14 | For more info: 15 | https://github.com/timbrel/GitSavvy/compare/2.20.0...2.21.0 16 | -------------------------------------------------------------------------------- /messages/2.22.0.txt: -------------------------------------------------------------------------------- 1 | GitSavvy 2.22.0 2 | =============== 3 | 4 | - Greatly improved graph view (`git: Repo History` or `git: File History` via 5 | the Command Palette) 6 | 7 | [ctrl+R] Goto symbols, tags, branches 8 | [h] Goto HEAD commit 9 | [enter] Open a menu with additonal actions, for example to checkout or 10 | remove a branch, reset HEAD, or to create or delete a tag. 11 | [o] To open the commit in a new view 12 | [m] Open or close a panel to always see the current commit details 13 | 14 | further experimental bindings 15 | [a] to toggle `--all` 16 | [f] to enter additional filter verbatim 17 | E.g. try `--reflog` or `-Ssearch_term` 18 | 19 | - Improved navigation with [oOg] throughout the application 20 | 21 | When viewing a commit: 22 | [o] To open the file revision at hunk position 23 | [O] To open the same file but currently checked out on disk 24 | [g] To open the graph showing the context of the commit 25 | 26 | When viewing a file revision: 27 | [o] To open the commit this revision belongs to 28 | [O] To open the same file but currently checked out on disk 29 | [g] To open the graph showing the context of said commit 30 | 31 | From the status dashboard 32 | [g] To open the graph showing HEAD 33 | 34 | From the branches dashboard 35 | [g] To open the graph showing the selected branch 36 | 37 | 38 | For more info: 39 | https://github.com/timbrel/GitSavvy/compare/2.21.0...2.22.0 40 | -------------------------------------------------------------------------------- /messages/2.23.0.txt: -------------------------------------------------------------------------------- 1 | GitSavvy 2.23.0 2 | =============== 3 | 4 | - Improved "inline diff" experience (`git: diff current file inline`) (#1306) 5 | 6 | It is recommended to bind `gs_inline_diff` to a key combo so it becomes a main 7 | entry point into the GitSavvy world. E.g. 8 | 9 | ``` 10 | { 11 | "keys": ["ctrl+shift+["], 12 | "command": "gs_inline_diff", 13 | }, 14 | ``` 15 | 16 | Now, this key acts like a toggle. You press it to switch to the inline view. 17 | Press it again to close it. This should give you a experience very close to 18 | Sublime Text's built in `toggle_inline_diff` ("Show Diff Hunk") feature. 19 | 20 | However, being in that view, you can of course stage, undo staging, or discard 21 | changes. 22 | 23 | Use `[TAB]` to switch between the staged and unstaged area. 24 | 25 | Use `[c]` (commit stage), `[C]` (commit -a) or `[m]` (amend commit from stage) 26 | to enter the commit message view. Tip: These commands are also available for 27 | the "normal" diff view `git: diff` (command name: `gs_diff`). 28 | 29 | Notable: We now use the builtin color scopes (just like the intra line 30 | colorization for the normal diff views does). The Sublime Text scopes (e.g. 31 | "diff.inserted.char") are suffixed with ".git-savvy.inline-diff". 32 | 33 | A lot of bugs have been fixed for this view as well. 34 | 35 | 36 | - GitSavvy learned a new command `gs_stage_hunk` (#1305) 37 | 38 | The command works from normal views t.i. while editing your files. Note that 39 | we don't ship any bindings. If you want to use this feature, it is expected you 40 | bind it to a key combo, the Command Palette, or maybe the context menu on your 41 | own. E.g. 42 | 43 | ``` 44 | { "keys": ["ctrl+alt+s"], "command": "gs_stage_hunk"}, 45 | ``` 46 | 47 | Now having this binding, you can stage "hunks" directly from the file. 48 | This works best with the default Sublime Text settings: 49 | 50 | ``` 51 | "mini_diff": true, // or "auto" 52 | "show_git_status": true, 53 | "git_diff_target": "index", // ! 54 | ``` 55 | 56 | With these settings, the Sublime gutter will show you the modified lines. 57 | 58 | Just try it! Edit some lines, hit the key combo, and the gutter will reflect 59 | that immediately. Btw, the commands supports single and multiple cursors, and 60 | single or multiple selections. 61 | 62 | If that's your next quick workflow, maybe consider the following binding 63 | 64 | ``` 65 | { 66 | "keys": ["ctrl+shift+["], 67 | "command": "gs_inline_diff", 68 | "args": { "cached": true } 69 | }, 70 | ``` 71 | 72 | so you can quickly see the staged hunks, maybe to unstage, but of course to 73 | enter the commit process using `[cCm]`. 74 | 75 | 76 | For more info: 77 | https://github.com/timbrel/GitSavvy/compare/2.22.0...2.23.0 78 | 79 | -------------------------------------------------------------------------------- /messages/2.25.0.txt: -------------------------------------------------------------------------------- 1 | GitSavvy 2.25.0 2 | =============== 3 | 4 | - Improve historical "file view" 5 | 6 | When you look at a specific revision of a file (for example via 7 | `git: show current file at commit`) we now show diff markers in the gutter 8 | area. 9 | 10 | Use `[p]`/`[n]` to switch to a newer or older version of that file. You 11 | can also open an "inline diff" from here, using the Command Palette or 12 | preferable a key binding. 13 | 14 | A more typical work-flow to such a file revision is from a "graph view", 15 | either the `git: Repo History` or `git: File History`: press `[o]` to show 16 | the full commit, and then on a specific hunk `[o]` again to show the 17 | complete file at that revision. 18 | 19 | - You can now generally switch from the "normal" diff view to the 20 | "inline diff". This is useful if you want to see more of the context of a 21 | hunk. Esp. it enables you to stage single lines of a hunk which is still 22 | not implemented for the "normal" diff. 23 | 24 | 25 | 26 | For more info: 27 | https://github.com/timbrel/GitSavvy/compare/2.24.0...2.25.0 28 | -------------------------------------------------------------------------------- /messages/2.26.0.txt: -------------------------------------------------------------------------------- 1 | GitSavvy 2.26.0 2 | =============== 3 | 4 | - Implement `git: Line History` 5 | 6 | A "Line History" (aka "log wtf" or "log why") basically calls 7 | "git log -Lx,y:". This is a supereasy to use tool in 8 | the editor. It is usually the faster (in terms of you get the info 9 | you're looking for faster) "blame" and also the faster "File History" 10 | because often you're only interested in some part of a file. Really 11 | answering "Why is this line or section of code here?". 12 | 13 | The command can be called from normal views only. Just put your cursor 14 | somewhere, or drag a selection, and call the command from the Command Palette. 15 | The command is called `gs_line_history` if you want to bind it to a key. 16 | 17 | Comes with: 18 | `[o]` to open the complete commit under the cursor 19 | `[O]` to open the file at that revision 20 | `[g]` to open the graph context 21 | 22 | 23 | - Improvements to the Repo History 24 | 25 | We already showed the path down while navigating through the history. Now 26 | we also follow and colorize the path upwards. (For customization: the scopes 27 | we use here are `git_savvy.graph.dot.above`, `git_savvy.graph.path_char.above`) 28 | 29 | This is not just fancy but allows easier and faster navigation: 30 | 31 | Use the arrow keys `[up]` and `[down]` to get to the previous or next commit. 32 | Use `[alt+up]` or `[alt+down]` for "wide" jumps usually following the first parent. 33 | After such a wide jump, you can use the built-in `jump_back`/`jump_forward` 34 | commands (for example `[ctrl+-]` on Windows/Linux by default) to jump back 35 | to the previous location. 36 | 37 | 38 | There is also a new smart copy function. Just invoke the builtin "copy" 39 | command (for example `[ctrl+c]`) to directly copy the commit hash, or commit 40 | message, or a combination of both to the clipboard (if nothing is selected). 41 | 42 | 43 | Minor: If on a HEAD commit we offer "pull", "push", and "fetch" in the 44 | actions menu (`[enter]`), and the diff action opens the normal diff for staging, 45 | unstaging etc. 46 | 47 | Finally, we now clearly mark fixup/squash commits. (The scope here is: 48 | `string.other.rebase-hint.git-savvy`.) 49 | 50 | 51 | - Improvements to the Commit Message View 52 | 53 | Being in the diff area, you can now use `[o]` on a hunk to open that location 54 | for editing. Also `[,.]` for navigation per hunk. But unstaging etc. is not 55 | supported yet. 56 | 57 | 58 | - Noteable 59 | 60 | Removed "word-diff" switch from the Diff view. This has been superseeded 61 | by the intraline diffing feature. There is still the switch to ignore white- 62 | space changes though, but the keybinding for that moved (from `[s]`) to `[w]`. 63 | 64 | The setting "show_commit_diff" now defaults to "full" (was: "stat"). 65 | The setting "show_full_commit_info" now defaults to "true" (was: "false"). 66 | 67 | 68 | For more info: 69 | https://github.com/timbrel/GitSavvy/compare/2.25.0...2.26.0 70 | -------------------------------------------------------------------------------- /messages/2.28.0.txt: -------------------------------------------------------------------------------- 1 | GitSavvy 2.28.0 2 | =============== 3 | 4 | - Improvements to the Diff View 5 | 6 | The standard diff view now supports staging, unstaging, or discarding 7 | selections, for example single lines, and whole files. 8 | 9 | In consistency with the other views we bind `[sud]` to stage, unstage, 10 | or discard one hunk or the concrete selection. This works with multiple 11 | cursors and selections as well. 12 | 13 | Use the uppercase keys `[SUD]` to operate on whole files. 14 | 15 | "Zooming", t.i. changing how many context lines are shown, via `[+-]` has 16 | been greatly improved. It's actually useable and useful now. 17 | 18 | **Tip** If you want to stage several single lines from the same chunk, 19 | it is recommended to make use of multiple selections. We take that as a 20 | hint on which lines actually belong together and form symmetrical 21 | modifications. 22 | 23 | 24 | With this release we start to ship a series of refactorings as well. As a 25 | result, a few settings have been removed: 26 | 27 | - `live_panel_output` and `live_panel_output_timeout` as the feature has 28 | been reimplemented. We now always update the panel "live" if possible. 29 | 30 | - `close_panel_for` as this feature was poorly implemented. 31 | 32 | - `show_input_in_output`, `show_stdin_in_output`, `show_time_elapsed_in_output` 33 | 34 | You should not use them anymore and remove them from your user settings file. 35 | 36 | 37 | 38 | For more info: 39 | https://github.com/timbrel/GitSavvy/compare/2.27.7...2.28.0 40 | -------------------------------------------------------------------------------- /messages/2.3.0.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.2.1: 2 | 3 | Feature: 4 | - Git-Flow commands now supported (but disabled by default). 5 | - `git reset`, using either commit log or branch reflog. 6 | - Custom git commands now supported (see docs in README). 7 | - New branch history comparison in the Branch dashboard. 8 | 9 | Enhancement: 10 | - Rebase dashboard now handles case where master doesn't exist. 11 | - Add option to prompt before discarding and closing commit message window. 12 | 13 | Fix: 14 | - Several issues with inline-diff view behavior. BIG THANKS to @stoivo! 15 | - Branch matching regex did not match dots in remote branch name. 16 | - Branch dashboard failed to show branch when commit includes "..". 17 | - Rebase conflict shortcuts did not execute anywhere on line. 18 | - Stash shortcuts did not execute when cursor was at end of line. 19 | - Git fetch did not retrieve all data when no remote specified. 20 | - `global_flags` were not always placed correctly in Git command. 21 | 22 | Other: 23 | - Update contributor guidelines. 24 | 25 | Contributors: 26 | - Simon (@stoivo) 27 | - David Devlin 28 | - Pavel Savchenko 29 | - Dale Bustad 30 | -------------------------------------------------------------------------------- /messages/2.30.0.txt: -------------------------------------------------------------------------------- 1 | GitSavvy 2.30.0 2 | =============== 3 | 4 | Two simple, quality-of-life improvements: 5 | 6 | - Open an GitHub issue or PR in the browser 7 | 8 | Whenever you see a link to an issue, for example looking at the Repo 9 | History or an individual commit, either hover over it to open a popup, 10 | or place the cursor on it and hit `[o]` to open the issue/PR page in 11 | your browser. (#1483) 12 | 13 | 14 | - Simplify wizard when pushing to a remote 15 | 16 | When pushing a branch to a remote the first time, for example prior 17 | to creating a PR on GitHub, we now guess a good remote and assume you 18 | don't want to have a different branch name than the local one on the 19 | remote. (#1475) 20 | 21 | 22 | Notable 23 | 24 | - For compatibility with the new Sublime Text 4, introduce syntax and 25 | settings for the basic output panel. And while we're at it, add some 26 | color touch. (#1484) 27 | - Correctly find and resolve `git_dir` for *workspaces*. (#1477) 28 | - Refactor how we update the workdir status and update the status dashboard 29 | in the background. Basically introducing a *reactive* framework. (#1478) 30 | 31 | 32 | Tip: Subscribe to the GitSavvy "releases" on GitHub to get a notification 33 | about a new version, and not be surprised on the next Sublime restart. 34 | 35 | :love: 36 | -------------------------------------------------------------------------------- /messages/2.31.0.txt: -------------------------------------------------------------------------------- 1 | GitSavvy 2.31.0 2 | =============== 3 | 4 | - Revamped `git: clone` and `git: init` 5 | 6 | Both commands now find sane root folders for the ".git" repository. 7 | 8 | For `git: clone`, we suggest a sibling folder of the current open folder in 9 | the side-bar, and if there is none we fallback to `~`. 10 | 11 | Try it! Copy a URL from your browser, or for example 12 | 13 | `https://github.com/timbrel/GitSavvy/blob/master/README.md` 14 | 15 | Open a new window (`ctrl+shift+n`), open the Command Palette (`ctrl+p`), 16 | `git: clone`. And just leave the defaults. 17 | 18 | The idea is: Whenever you read code on GitHub, and it's getting complicated, 19 | grab it and read it in Sublime instead. You can "Go To Definition"s back and 20 | forth way faster. 21 | 22 | 23 | Want to contribute back? 24 | 25 | It's actually easy. Here's the workflow: `checkout new branch` (`cnb`), edit, 26 | then commit as usual. `create fork`, `create pull request` (`cpr`). Done. 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /messages/2.34.0.txt: -------------------------------------------------------------------------------- 1 | GitSavvy 2.34.0 2 | =============== 3 | 4 | Small things matter! 5 | 6 | - Add fixup helpers (bound to `[f]`) to the `diff`, `inline-diff`, and 7 | `Line History`. (#1567) 8 | 9 | - In the `Commit View` and `Line History` bind `[h]` to open the commit on 10 | GitHub in the browser. Also add a clickable annotation in the top right 11 | corner. (#1570) 12 | 13 | - For `github: open file on remote`, usually open the last-modified revision 14 | of the file. (#1571) 15 | 16 | - Automatically treat spaces as `-` (dashes) when asking for a new branch 17 | name. (#1557) 18 | 19 | - Add command to Command Palette (`git: rename current branch`) to rename 20 | the current, active branch. (#1558) 21 | 22 | - Add command `git: unset tracking branch` to remove the tracking information 23 | of the current branch (#1559) 24 | 25 | - Allow deletion of the currently checked out ("active") branch. (#1560) 26 | 27 | - Smooth(er), incidental drawing of the log graph view. (#1569) 28 | 29 | Indicate visually that we wait for a response from `git log graph` if it 30 | takes too long, or that we await and expect a complete re-draw (typical 31 | because a filter setting changed). 32 | 33 | - Make filenames clickable in views where we show patches. (#1556) 34 | 35 | - Mark the commit message view as lintable by SublimeLinter. (#1566) 36 | 37 | 38 | 39 | Fixes: 40 | 41 | - Fix validating branch name (#1554) 42 | - Fix diffstat colorization (#1555) 43 | - Ensure we checkout on the UI thread ("blocking") (#1561) 44 | - Fix highlighting of github usernames (#1562) 45 | - Fix commit message subject scopes and highlighting (#1563) 46 | - Fix intra line colorization of merge conflicted files (#1564) 47 | - Allow rebasing the initial (root) commit. (#1574) 48 | -------------------------------------------------------------------------------- /messages/2.36.0.txt: -------------------------------------------------------------------------------- 1 | GitSavvy 2.36.0 2 | =============== 3 | 4 | Insane refactorings went into this one. And you don't even see them. 5 | Well, 🤞, at least I hope so. 6 | 7 | Still some user facing things of course: 8 | 9 | - Re-designed the "git: tag" dashboard 10 | 11 | - In the "Repo History" it is now possible to fast-forward a branch. 12 | 13 | Say you're on a feature branch checked out, and you know that e.g. 14 | your "main" branch moved. 15 | 16 | You can now just select the main branch, hit `[enter]` for the menu, 17 | and select `Fast forward 'main' to 'origin/main'` if you already 18 | fetched a new tip. Otherwise it says `Update 'main' from 'origin/main'` 19 | and fetches first before moving the branch. 20 | 21 | You stay on the feature branch during that process, and can rebase 22 | immediately after that. 23 | 24 | Very useful. 25 | 26 | 27 | Some of you might love and even rely on this little package GitSavvy, 28 | and I do drink coffee just like others. If that's a match consider 29 | donating at https://paypal.me/herrkaste 😉 30 | 31 | 32 | 🍁🍂 33 | 34 | Full release notes are on https://github.com/timbrel/GitSavvy/releases 35 | Just because I see I forgot to send any for 2.35.0 36 | -------------------------------------------------------------------------------- /messages/2.38.0.txt: -------------------------------------------------------------------------------- 1 | GitSavvy 2.38.0 2 | =============== 3 | 4 | 5 | - The status dashboard has a slightly different design and better cursor 6 | management. Generally switching between the dashboards should be snappier. 7 | 8 | - `Repo History`s rebase menu (`[r]`) learned `--rebase-merges` and 9 | `--update-refs` tricks. Note that the latter requires git v2.38.0[*]. 10 | 11 | With that we have sad news: the stand-alone "rebase" dashboard has been 12 | deprecated. Yep. 13 | 14 | The standard menu (`[enter]`) though has learned "Create branch " and 15 | "Move active branch to ". 16 | 17 | - In the commit message view, you can directly unstage or discard hunks or 18 | lines from the diff shown there. The former is very needed when amending 19 | or splitting commits while rebasing. You just start a "commit --amend" and 20 | deselect what you don't want to commit yet. Discarding is useful if you have 21 | these "oh, there's still a console.log in there"; just reset those lines 22 | quickly. 23 | 24 | - You can now open the `Line History` from basically everywhere you see a 25 | patch/diff and it will read the commit and correct line numbers from it. 26 | 27 | E.g. you're in the normal diff view with a small fixup. `[S]`tage that, the 28 | diff will switch to the staged mode automatically after the last hunk. Now 29 | initiate `Line History` from the command palette to bring up a new view 30 | showing the history of the hunk. Search for the right commit and hit `[f]` 31 | to make a fixup commit. 32 | 33 | - Hunk-to-hunk navigation has been improved in all views showing diffs/patches. 34 | 35 | - Staging deletion-only hunks has been made easier for the `gs_stage_hunk` 36 | command. 37 | 38 | 39 | 🎄🧑‍🎄 40 | 41 | 42 | Quick workflow reminders. As GitSavvy generally does not come with *global* 43 | key-bindings, you have to make your own ones in your `Key Bindings` file. 44 | 45 | Just start a commit from everywhere. If nothing is staged yet, "commit --all" 46 | is assumed: 47 | 48 | ``` 49 | { "keys": ["ctrl+shift+c"], "command": "gs_commit"}, 50 | ``` 51 | 52 | Stage something quickly, directly in the edited buffer: 53 | 54 | ``` 55 | { "keys": ["alt+s"], "command": "gs_stage_hunk"}, 56 | ``` 57 | 58 | Set `"git_diff_target": "index",` in your Sublime Text's preferences too, so 59 | that the gutter shows the correct information, namely changed unstaged lines. 60 | 61 | Navigate from hunk to hunk in your normal view: 62 | 63 | ``` 64 | { "keys": ["ctrl+."], "command": "gs_next_hunk" }, 65 | { "keys": ["ctrl+,"], "command": "gs_prev_hunk" }, 66 | ``` 67 | 68 | To open a diff for the current file is still very useful: 69 | 70 | ``` 71 | { 72 | "keys": ["ctrl+shift+."], "command": "gs_diff", 73 | "args": { "current_file": true } 74 | }, 75 | ``` 76 | 77 | Acts btw as a toggle, t.i. when viewing a diff closes the diff, otherwise opens 78 | it. Opens a "diff all files" when used from e.g. the `Repo History`! 79 | 80 | Quickly open a Line History: 81 | 82 | ``` 83 | { "keys": ["ctrl+shift+l"], "command": "gs_line_history"}, 84 | ``` 85 | 86 | 87 | 88 | [*] Yeah GitSavvy and git have the same version number currently 89 | -------------------------------------------------------------------------------- /messages/2.39.0.txt: -------------------------------------------------------------------------------- 1 | GitSavvy 2.39.0 2 | =============== 3 | 4 | 5 | | Likely the last version working with Sublime Text 3. If you're still using that | 6 | | freeze your GitSavvy install. | 7 | 8 | 9 | - This version implements `[n]`ext/`[p]`revious navigation for the inline diff and 10 | the commit views including the commit panel at the bottom of the graph views. 11 | 12 | E.g., you could open the inline diff for a given file, then `[p][O]` to see 13 | the last revision of that exact file. 14 | 15 | - @zebyja implemented variable expansion especially for the custom env/PATH setting. 16 | E.g. in your GitSavvy settings: 17 | 18 | ``` 19 | { 20 | "env": { 21 | "PATH":"venv/bin:${PATH}" 22 | }, 23 | } 24 | ``` 25 | 26 | to *prepend* `venv/bin`. (#1724) 27 | 28 | 29 | -------------------------------------------------------------------------------- /messages/2.4.0.txt: -------------------------------------------------------------------------------- 1 | This is a smaller release, but includes fixes some folks have been waiting on. 2 | 3 | Changes since 2.3.0: 4 | 5 | Enhancement: 6 | - Additional `git: blame` display options.options. 7 | - Debug/logging commands have been renamed. 8 | - `git_path` can now be customized on a per-platform basis. 9 | - Add `merge_log` setting to toggle `--log` inclusion in `git merge`. 10 | 11 | Bug: 12 | - Provide fallcack for when Git command decoding fails. 13 | - `GitSavvy: help` no longer fails in Inline-Diff mode. 14 | - Custom commands would not always be invoked properly. 15 | - Github integration would not work for remotes with underscore characters. 16 | - GitSavvy now fails more gracefully when previously existing repo is deleted. 17 | 18 | Contributors: 19 | - Pavel Savchenko 20 | - John Burnett 21 | - Simon 22 | - Dale Bustad 23 | - David Devlin 24 | -------------------------------------------------------------------------------- /messages/2.41.0.txt: -------------------------------------------------------------------------------- 1 | GitSavvy 2.41.0 2 | =============== 3 | 4 | Minor, quality-of-life improvements 5 | 6 | - Improved cursor placement after staging, unstaging, etc., in the diff view for better orientation. 7 | - When transitioning from a normal view (buffer) to the diff view, the just edited hunk is now 8 | placed and shown automatically (if applicable). We had this for the inline diff view like 9 | forever and now also for the standard diff view. This is even more interesting if you added a 10 | key binding 11 | 12 | ``` 13 | { 14 | "keys": ["ctrl+shift+."], 15 | "command": "gs_diff", 16 | "args": { "current_file": true } 17 | }, 18 | ``` 19 | 20 | 21 | 22 | Less minor 23 | 24 | In the commit message view, you can now unstage directly even in the `-a` (all) mode, completing this feature set. 25 | 26 | In the Repo History (graph) views, 27 | 28 | - an overview mode has been added, triggered by pressing `[s]`. This mode hides all commits and 29 | only displays branch tips and reachable tags. (In this mode, pressing `[a]` will also hide the 30 | tags!) 31 | - an asterisk (`*`) after `HEAD` now indicates that the repository is currently dirty. For example, 32 | it will print `(HEAD* -> master)` now. 😐😏 33 | - Special branches in a repository are now marked with a special syntax scope, allowing you to 34 | assign a different color to them. The branch names that are considered special are: 35 | `(master|main|dev|trunk)\b`, and the scope is named 36 | `constant.other.git.branch.special.git-savvy`. 37 | Note that you need to adjust your color scheme to see any change; GitSavvy will not modify it 38 | automatically. 39 | 40 | 41 | Sincerely, 42 | 😘 43 | 44 | You can send some virtual coffee too: https://paypal.me/herrkaste 45 | 46 | -------------------------------------------------------------------------------- /messages/2.42.0.txt: -------------------------------------------------------------------------------- 1 | GitSavvy 2.42.0 2 | =============== 3 | 4 | This release mostly consists of bug- and glitch-fixes you might not even noticed. 5 | 6 | Few notable changes: 7 | 8 | - GitSavvy's output panel now supports progress information. This was trickier than I thought it 9 | would get, and especially makes "cloning" or "rebasing" much nicer to look at. 10 | 11 | - In the commit view, "select all" (e.g. `ctrl+a`) now selects just your commit message and not the 12 | help text and diff below anymore. That only applies when the cursor is within the message of 13 | course. `ctrl+a` twice will still select the whole buffer. 14 | 15 | - Forking a GitHub repository will now by default only copy the default branch. This is 16 | explained on their blog (https://github.blog/changelog/2022-07-27-you-can-now-fork-a-repo-and-copy-only-the-default-branch/). 17 | However, this is also pluggable, please refer to GitSavvy's settings (https://github.com/timbrel/GitSavvy/commit/aa555a22#diff-96ea80b9a1df2392ccf6a341d0375dd230076a620a236c127f7980c15c2ab5f8). 18 | 19 | 20 | 💕ly greetings 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /messages/2.5.0.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.4.0: 2 | 3 | Enhancement: 4 | - Pre-select first file in status dashboard on initial creation. 5 | - Add navigation to files in status dashboard. 6 | 7 | Documentation: 8 | - Add note about Git path in Windows 64-bit. 9 | 10 | Contributors: 11 | - Dale Bustad 12 | -------------------------------------------------------------------------------- /messages/2.6.0.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.5.0: 2 | 3 | Fix: 4 | - Update status dashboard syntax to highlight , and . 5 | - Status dashboard , and . can navigate to stashes. 6 | 7 | Enhancement: 8 | - Display message to user if fallback decoding fails. 9 | - Add option to confirm `git push --force`. 10 | 11 | Contributors: 12 | - David Mohl 13 | - Pavel Savchenko 14 | - Dale Bustad 15 | -------------------------------------------------------------------------------- /messages/2.7.0.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.6.0: 2 | 3 | Enhancement: 4 | - Pressing `o` from the inline-diff will open the file at the same cursor position. 5 | - `git: graph` view can now be refreshed. 6 | - `commit_on_close` configuration option added more Git-like commit view experience. 7 | 8 | Bug: 9 | - Branch dashboard would not display branches ending in hyphen char. 10 | 11 | Contributors: 12 | - Dale Bustad 13 | - Simon 14 | -------------------------------------------------------------------------------- /messages/2.8.0.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.7.1: 2 | 3 | Feature: 4 | - Press Tab to transition between dashboards in a single view (or Shift-Tab for reverse) (configurable). 5 | - Add a simple stand-alone cherry-pick command. 6 | - Diff view can toggle `ignore_all_space` and `word_diff` options. 7 | - Show and edit branch descriptions in branch dashboard. 8 | - Add international codec support including GBK, BIG5, EUC-KR, EUC-JP, etc. 9 | - Open issues and repo page on GitHub remote through command palette. 10 | 11 | Enhancement: 12 | - Rebase dashboard supports non-branch base. 13 | - Add syntax highlighting for `git: graph` view. 14 | - Add option to auto-close panel for specified Git operations. 15 | - Add key-binding to push from status dashboard. 16 | - Fork UX in rebase-conflict scenario when one side is deleted. 17 | - Add syntax highlighting for comments and tracking info in branch dashboard. 18 | 19 | Fix: 20 | - Add empty line to beginning of tags inferface. 21 | - Always pass the branch name on git flow * finish (fixes issues with certain git-flow releases). 22 | - Attempting to discard staged file needn't trigger destructive-action warning. 23 | - Potential issue with interfaces sharing region keys. 24 | - Remote duplicate branch names when determining remote branches. 25 | - `git:pull` would fail if ORIGIN/HEAD known. 26 | - All types of unmerged conflicts were not shown correctly. 27 | - Unintentional default settings for show_panel_for and close_panel_for. 28 | - Dashboard syntax did not support multi-character key name. 29 | 30 | Documentation: 31 | - Define lowest-supported Git version. 32 | 33 | Housekeeping: 34 | - Port tags dashboard to interface pattern. 35 | 36 | 37 | Contributors: 38 | - Pavel Savchenko 39 | - Dale Bustad 40 | - Simon 41 | - marcinruszkiewicz 42 | -------------------------------------------------------------------------------- /messages/2.9.0.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.8.1: 2 | 3 | Feature: 4 | - `git: log current file` now has an option to view file at chosen commit.commit 5 | - Add `git: push all tags` command. 6 | - New `git: smart tag` commands for easy semantic versioning. 7 | - Directly checkout or view diff of GitHub pull requests with `github: review pull request`. 8 | - Basic support for opening new GitHub pull request from GitSavvy. 9 | 10 | Enhancement: 11 | - User is prompted for reset type (with hints) after running `git: reset`. 12 | 13 | Fix: 14 | - Editing commit message would fail if previous content deleted before new content entered. 15 | - Dashboards would not refresh after commit if commit_on_close enabled. 16 | - GitSavvy would attempt to open untracked file on remote. 17 | - Pushing a single tag would not work in tag dashboard. 18 | - `git: fetch` could not fetch commit directly. 19 | - Unclear destructive-action prompt for status-dashboard `D`. 20 | - Endless loop when some poor soul tries to diff a binary file as text. 21 | - Rebase dashboard failed if no directories added to active window. 22 | - Incorrect syntax highlighting in docs/README.md 23 | - Inline-diff view would sometimes jump to the right after staging hunks. 24 | - Refresh command in status dashboard was incorrectly wired-up. 25 | 26 | Other: 27 | - Add a GitSavvy diff syntax witch supports word diffs. 28 | 29 | Contributors: 30 | - Pavel Savchenko 31 | - Steve Bennett 32 | - Dale Bustad 33 | - Craig Campbell 34 | - Nicolás Santángelo 35 | - Randy Lai 36 | - David Devlin 37 | -------------------------------------------------------------------------------- /messages/install.txt: -------------------------------------------------------------------------------- 1 | # Welcome to GitSavvy 2 | 3 | GitSavvy provides integration between Sublime Text 3 and Git. 4 | 5 | If you're a new user and you find yourself asking, _"Can GitSavvy do X?"_, there are two places to look. First, open your command palette and type `GitSavvy: help`. This will take you to the offline wiki-style documentation. 6 | 7 | You can also check for configurability options by opening `Preferences > Package Settings > GitSavvy > Settings - Default` in the ST3 menu. Any optional or configurable behavior will be documented here, along with the default values. 8 | 9 | Finally, if at any time you encounter a bug, or if you wish that GitSavvy included some missing functionality, please open an issue: 10 | https://github.com/timbrel/GitSavvy/issues 11 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "GitSavvy" 3 | readme = "README.md" 4 | requires-python = ">= 3.12" 5 | 6 | [tool.rye] 7 | managed = true 8 | virtual = true 9 | dev-dependencies = [ 10 | "flake8==5.*", 11 | ] 12 | -------------------------------------------------------------------------------- /requirements-dev.lock: -------------------------------------------------------------------------------- 1 | # generated by rye 2 | # use `rye lock` or `rye sync` to update this lockfile 3 | # 4 | # last locked with the following flags: 5 | # pre: false 6 | # features: [] 7 | # all-features: false 8 | # with-sources: false 9 | # generate-hashes: false 10 | # universal: false 11 | 12 | flake8==5.0.4 13 | mccabe==0.7.0 14 | # via flake8 15 | pycodestyle==2.9.1 16 | # via flake8 17 | pyflakes==2.5.0 18 | # via flake8 19 | -------------------------------------------------------------------------------- /requirements.lock: -------------------------------------------------------------------------------- 1 | # generated by rye 2 | # use `rye lock` or `rye sync` to update this lockfile 3 | # 4 | # last locked with the following flags: 5 | # pre: false 6 | # features: [] 7 | # all-features: false 8 | # with-sources: false 9 | # generate-hashes: false 10 | # universal: false 11 | 12 | -------------------------------------------------------------------------------- /syntax/blame.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // same on all syntaxes 3 | "auto_indent": false, 4 | "detect_indentation": false, 5 | "draw_indent_guides": false, 6 | "draw_white_space": "None", 7 | "gutter": false, 8 | "line_numbers": false, 9 | "margin": 20, 10 | "rulers": [], 11 | "translate_tabs_to_spaces": false, 12 | "word_wrap": false, 13 | // syntax specific 14 | "match_brackets": false, 15 | "match_tags": false 16 | } 17 | -------------------------------------------------------------------------------- /syntax/blame.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: GitSavvy Blame 5 | hidden: true 6 | scope: git-savvy.blame 7 | contexts: 8 | main: 9 | - match: ^(\-+ )(\|)( \-+)$ 10 | comment: separator 11 | scope: comment.block.git-savvy.splitter 12 | captures: 13 | 1: comment.block.git-savvy.splitter.horizontal.commit-info 14 | 2: comment.block.git-savvy.splitter.vertical 15 | 3: comment.block.git-savvy.splitter.horizontal.source 16 | 17 | - match: ^Not committed yet.+?\s+ 18 | scope: meta.not-committed.blame.git-savvy 19 | push: right-column 20 | 21 | - match: ^([0-9a-f]{6,40})\s+ (\(CURRENT COMMIT\))? 22 | comment: SHA 23 | scope: meta.blame-line.git-savvy 24 | captures: 25 | 1: constant.numeric.commit-hash.git-savvy 26 | 2: meta.current-commit.blame.git-savvy 27 | push: right-column 28 | 29 | - match: ^([^|]+) (<)(\S*?)(>|\.{3})\s+ 30 | comment: name + email 31 | scope: meta.blame-line.git-savvy 32 | captures: 33 | 1: entity.name.tag.git-savvy 34 | 2: punctuation.definition.other.begin.git-savvy 35 | 3: string.other.mail.git-savvy 36 | 4: punctuation.definition.other.end.git-savvy 37 | push: right-column 38 | 39 | - match: ^.+?(?=\s\|\s) 40 | scope: comment.block.git-savvy.commit-info 41 | push: right-column 42 | 43 | right-column: 44 | - match: (\|)\s+(\d*).*$ 45 | captures: 46 | 1: comment.block.git-savvy.splitter.vertical 47 | 2: constant.numeric.line-number.blame.git-savvy 48 | - match: $ 49 | pop: true 50 | -------------------------------------------------------------------------------- /syntax/branch.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // same on all syntaxes 3 | "auto_indent": false, 4 | "detect_indentation": false, 5 | "draw_indent_guides": false, 6 | "draw_white_space": "None", 7 | "gutter": false, 8 | "line_numbers": false, 9 | "margin": 20, 10 | "rulers": [], 11 | "translate_tabs_to_spaces": false, 12 | "word_wrap": false, 13 | 14 | // syntax specific 15 | "gutter": true, 16 | "margin": -8, 17 | "highlight_line": true, 18 | "fold_buttons": false, 19 | "fade_fold_buttons": false, 20 | "match_brackets": false, 21 | "match_tags": false 22 | } 23 | -------------------------------------------------------------------------------- /syntax/branch.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: GitSavvy Branch 5 | hidden: true 6 | scope: git-savvy.branch 7 | contexts: 8 | main: 9 | - include: "Packages/GitSavvy/syntax/dashboard.sublime-syntax" 10 | 11 | - match: '^ LOCAL:$' 12 | scope: keyword.other.git-savvy.section-header.branch.local 13 | push: 14 | - meta_scope: meta.git-savvy.status.section.branch.local 15 | - include: section 16 | 17 | - match: '^ REMOTE (\()([^\)]+)(\)):$' 18 | scope: keyword.other.git-savvy.section-header.branch.remote 19 | captures: 20 | 1: punctuation.definition.git-savvy.section-header.remote 21 | 2: keyword.other.git-savvy.section-header.branch.remote.name 22 | 3: punctuation.definition.git-savvy.section-header.remote 23 | push: 24 | - meta_scope: meta.git-savvy.status.section.branch.remote 25 | - include: section 26 | 27 | section: 28 | - match: ^$ 29 | pop: true 30 | 31 | - match: (?=^\s+▸) 32 | push: 33 | - meta_scope: meta.git-savvy.branches.branch.active-branch 34 | - include: row 35 | - match: $ 36 | pop: true 37 | 38 | - match: ^ 39 | push: 40 | - include: row 41 | - match: $ 42 | pop: true 43 | 44 | row: 45 | - match: '^ \s+(\\ )?(checked out at: )(.+)$' 46 | captures: 47 | 0: keyword 48 | 1: keyword 49 | 2: keyword 50 | 3: keyword.other.git-savvy.path 51 | 52 | 53 | - match: '^\s+(<)([0-9a-f]{7,40}) ?(>-?) (.*)$' 54 | captures: 55 | 0: meta.git-savvy.branches.branch.as-worktree 56 | 1: punctuation.symbol.foreign-branch 57 | 2: constant.other.git-savvy.branches.branch.sha1 keyword 58 | 3: punctuation.symbol.foreign-branch 59 | 4: keyword 60 | 61 | - match: '^\s+(<)([0-9a-f]{7,40})(>) (\S+)\s?(.*)$' 62 | captures: 63 | 64 | 0: meta.git-savvy.branches.branch.as-worktree 65 | 1: punctuation.symbol.foreign-branch 66 | 2: constant.other.git-savvy.branches.branch.sha1 keyword 67 | 3: punctuation.symbol.foreign-branch 68 | 4: meta.git-savvy.branches.branch.name gitsavvy.gotosymbol 69 | 5: comment.git-savvy.branches.branch.extra-info 70 | 71 | - match: '^ (▸)?\s+([0-9a-f]{7,40}) ([^\s]+)\s([^(-].+?)?((?:, )?\(.+?(?:, (gone))?\))?( ?\- .+)?$' 72 | captures: 73 | 0: meta.git-savvy.branches.branch 74 | 1: punctuation.symbol.active-branch.git-savvy 75 | 2: constant.other.git-savvy.branches.branch.sha1 76 | 3: meta.git-savvy.branches.branch.name gitsavvy.gotosymbol 77 | 4: comment.git-savvy.branches.branch.date 78 | 5: comment.git-savvy.branches.branch.tracking-info 79 | 6: constant.git-savvy.upstream.gone 80 | 7: comment.git-savvy.branches.branch.description keyword 81 | -------------------------------------------------------------------------------- /syntax/diff.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // same on all syntaxes 3 | "auto_indent": false, 4 | "detect_indentation": false, 5 | "draw_indent_guides": false, 6 | "draw_white_space": "None", 7 | "gutter": false, 8 | "line_numbers": false, 9 | "margin": 20, 10 | "rulers": [], 11 | "translate_tabs_to_spaces": false, 12 | "word_wrap": false, 13 | 14 | // syntax specific 15 | "gutter": true, 16 | "margin": -8, 17 | "fold_buttons": false, 18 | "fade_fold_buttons": false, 19 | "match_brackets": false, 20 | "match_tags": false 21 | } 22 | -------------------------------------------------------------------------------- /syntax/diff_view.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // same on all syntaxes 3 | "auto_indent": false, 4 | "detect_indentation": false, 5 | "draw_indent_guides": false, 6 | "draw_white_space": "None", 7 | "gutter": false, 8 | "line_numbers": false, 9 | "margin": 20, 10 | "rulers": [], 11 | "translate_tabs_to_spaces": false, 12 | "word_wrap": false, 13 | 14 | // syntax specific 15 | "gutter": true, 16 | "margin": -8, 17 | "fold_buttons": false, 18 | "fade_fold_buttons": false, 19 | "match_brackets": false, 20 | "match_tags": false 21 | } 22 | -------------------------------------------------------------------------------- /syntax/diff_view.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: GitSavvy Diff View 5 | hidden: true 6 | scope: git-savvy.diff_view 7 | contexts: 8 | main: 9 | - match: ^ 10 | push: header 11 | 12 | header: 13 | - meta_scope: comment.header.git_savvy 14 | - match: ' STAGED CHANGES \(Will commit\)' 15 | scope: markup.deleted.diff 16 | - match: ' UNSTAGED CHANGES' 17 | - match: '^--$\n' 18 | set: 'scope:git-savvy.diff' 19 | -------------------------------------------------------------------------------- /syntax/graph.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // same on all syntaxes 3 | "auto_indent": false, 4 | "detect_indentation": false, 5 | "draw_indent_guides": false, 6 | "draw_white_space": "None", 7 | "gutter": false, 8 | "line_numbers": false, 9 | "margin": 20, 10 | "rulers": [], 11 | "translate_tabs_to_spaces": false, 12 | "word_wrap": false, 13 | "draw_unicode_white_space": "none", 14 | 15 | // syntax specific 16 | "gutter": true, 17 | "margin": -20, 18 | "highlight_line": true, 19 | "fold_buttons": true, 20 | "fade_fold_buttons": false, 21 | "match_brackets": false, 22 | "match_tags": false 23 | } 24 | -------------------------------------------------------------------------------- /syntax/help.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // same on all syntaxes 3 | "auto_indent": false, 4 | "detect_indentation": false, 5 | "draw_indent_guides": false, 6 | "draw_white_space": "None", 7 | "gutter": false, 8 | "line_numbers": false, 9 | "margin": 20, 10 | "rulers": [], 11 | "translate_tabs_to_spaces": false, 12 | "word_wrap": false, 13 | 14 | // syntax specific 15 | "gutter": true, 16 | "margin": -8, 17 | "highlight_line": true, 18 | "fold_buttons": false, 19 | "fade_fold_buttons": false, 20 | "match_brackets": false, 21 | "match_tags": false 22 | } 23 | -------------------------------------------------------------------------------- /syntax/help.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | name: GitSavvy Help 4 | hidden: true 5 | scope: git-savvy.help 6 | contexts: 7 | main: 8 | - meta_scope: comment.git-savvy.key-bindings-menu 9 | - match: '(##+) ([A-Z ]+)(?: (##+))?' 10 | scope: meta.git-savvy.key-bindings-header 11 | captures: 12 | 1: punctuation.definition.git-savvy.section 13 | 2: support.type.git-savvy.key-bindings-header-text 14 | 3: punctuation.definition.git-savvy.section 15 | 16 | - match: "#{3,}" 17 | scope: punctuation.definition.git-savvy.section 18 | 19 | - match: '(\[)([A-Za-z\,\.\-\+\?]+)(\])' 20 | scope: meta.git-savvy.key-bindings-key-stroke 21 | captures: 22 | 1: punctuation.definition.git-savvy.key-bindings-key-stroke 23 | 2: constant.character.git-savvy-key-binding-key 24 | 3: punctuation.definition.git-savvy.key-bindings-key-stroke 25 | push: 26 | - match: (?=\[|`|$) 27 | pop: true 28 | - match: \s+([\w\s,\(\)]+) 29 | captures: 30 | 1: comment.git-savvy.key-bindings-menu.key-binding-description 31 | 32 | - match: '(`)(.+)(`)' 33 | scope: meta.git-savvy.key-bindings-key-stroke 34 | captures: 35 | 0: constant.character.tag.git-savvy-key-binding-key 36 | 1: punctuation.definition.git-savvy.key-bindings-key-stroke 37 | 2: constant.character.git-savvy-key-binding-key 38 | 3: punctuation.definition.git-savvy.key-bindings-key-stroke 39 | 40 | - match: '(\*\*) ([^\*]+) (\*\*)' 41 | comment: ux note 42 | scope: support.type.git-savvy.ux-note 43 | captures: 44 | 1: punctuation.definition.git-savvy.note 45 | 2: support.type.git-savvy.ux-note.text 46 | 3: punctuation.definition.git-savvy.note 47 | 48 | 49 | -------------------------------------------------------------------------------- /syntax/make_commit.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // same on all syntaxes 3 | "auto_indent": false, 4 | "detect_indentation": false, 5 | "draw_indent_guides": false, 6 | "draw_white_space": "None", 7 | "gutter": false, 8 | "line_numbers": false, 9 | "margin": 20, 10 | "rulers": [], 11 | "translate_tabs_to_spaces": false, 12 | "word_wrap": false, 13 | // syntax specific 14 | "gutter": true, 15 | "line_numbers": true 16 | } 17 | -------------------------------------------------------------------------------- /syntax/make_commit.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: GitSavvy Make Commit 5 | hidden: true 6 | scope: git-savvy.make-commit 7 | 8 | contexts: 9 | main: 10 | - match: '(?=^## To make a commit.+)' 11 | set: dropped-content 12 | 13 | - match: ^ 14 | set: commit-subject 15 | 16 | references: 17 | - match: (?:\s|^)(\@[\w-]+) 18 | comment: github username 19 | captures: 20 | 1: constant.other.github-username.git-savvy.make-commit 21 | 22 | - match: '([\w-]+/[\w-]+)?#[0-9]+' 23 | comment: issue 24 | scope: constant.other.issue-ref.git-savvy.make-commit meta.git-savvy.issue-reference 25 | 26 | - match: '(?:[\s\[\(@])(\h{7,})\b' 27 | comment: sha reference 28 | captures: 29 | 1: constant.other.commit-sha.git-savvy.make-commit 30 | - match: '(?:")(\h{7,})(?!\B|\")' 31 | comment: sha reference 32 | captures: 33 | 1: constant.other.commit-sha.git-savvy.make-commit 34 | 35 | commit-subject: 36 | - meta_scope: meta.commit.message.subject markup.heading.subject.git.commit 37 | - match: (?=$)\n 38 | set: commit-line-separator 39 | - include: references 40 | 41 | commit-line-separator: 42 | - meta_content_scope: meta.commit.message 43 | - match: '(?=^## To make a commit.+)' 44 | set: dropped-content 45 | - match: \n 46 | set: commit-message 47 | 48 | commit-message: 49 | - meta_content_scope: meta.commit.message.body 50 | - include: references 51 | - match: '(?=^## To make a commit.+)' 52 | set: dropped-content 53 | 54 | dropped-content: 55 | - match: '^## To make a commit.+' 56 | scope: comment.help-text.git-savvy.make-commit 57 | set: 58 | - meta_scope: meta.dropped.git.commit 59 | - match: '(\[)(.+?)(\])' 60 | captures: 61 | 1: punctuation.definition.git-savvy.key-bindings-key-stroke 62 | 2: constant.character.git-savvy-key-binding-key 63 | 3: punctuation.definition.git-savvy.key-bindings-key-stroke 64 | - match: '(<)(.+?)(>)' 65 | - match: ^$ 66 | push: [scope:git-savvy.diff] 67 | - match: . 68 | scope: comment.help-text.git-savvy.make-commit 69 | -------------------------------------------------------------------------------- /syntax/output_panel.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "auto_indent": false, 3 | "detect_indentation": false, 4 | "draw_indent_guides": false, 5 | "draw_white_space": "None", 6 | "gutter": false, 7 | "is_widget": true, 8 | "line_numbers": false, 9 | "rulers": false, 10 | "scroll_past_end": false, 11 | "spell_check": false, 12 | "translate_tabs_to_spaces": false, 13 | "word_wrap": true, 14 | "wrap_width": 0, 15 | "wrap_width_style": "min" 16 | } 17 | -------------------------------------------------------------------------------- /syntax/output_panel.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: GitSavvy Output Panel 5 | hidden: true 6 | scope: output.git-savvy 7 | variables: 8 | sha: '\h{6,40}' 9 | contexts: 10 | main: 11 | - match: ^(\$)\s 12 | captures: 13 | 1: markup.heading entity.name.section 14 | 15 | - match: ^\[Done.*\]$ 16 | scope: string.other 17 | 18 | - match: (\b{{sha}}\^?) 19 | comment: SHA 20 | scope: constant.numeric.graph.commit-hash.git-savvy 21 | 22 | - match: \((\d+)/(\d+)\) 23 | comment: progress counter 24 | captures: 25 | 1: constant.numeric.progress.git-savvy 26 | 2: constant.numeric.progress.git-savvy 27 | 28 | - match: -> ([^ ]*) 29 | captures: 30 | 1: storage 31 | 32 | - match: \s(https?:\S+) 33 | captures: 34 | 1: markup.underline.link 35 | 36 | - match: ^hint:.* 37 | scope: string.other.hint.git-savvy 38 | 39 | - match: ^fatal:.* 40 | scope: markup.deleted.fatal.git-savvy 41 | -------------------------------------------------------------------------------- /syntax/rebase.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // same on all syntaxes 3 | "auto_indent": false, 4 | "detect_indentation": false, 5 | "draw_indent_guides": false, 6 | "draw_white_space": "None", 7 | "gutter": false, 8 | "line_numbers": false, 9 | "margin": 20, 10 | "rulers": [], 11 | "translate_tabs_to_spaces": false, 12 | "word_wrap": false, 13 | 14 | // syntax specific 15 | "gutter": true, 16 | "margin": -20, 17 | "highlight_line": true, 18 | "fold_buttons": true, 19 | "fade_fold_buttons": false, 20 | "match_brackets": false, 21 | "match_tags": false 22 | } 23 | -------------------------------------------------------------------------------- /syntax/rebase.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: GitSavvy Rebase 5 | hidden: true 6 | scope: git-savvy.rebase 7 | contexts: 8 | main: 9 | - include: "Packages/GitSavvy/syntax/dashboard.sublime-syntax" 10 | - match: "^ REBASE:" 11 | comment: Rebase summary 12 | scope: comment.git-savvy.summary-header.title.rebase 13 | push: 14 | - meta_scope: meta.git-savvy.rebase-summary 15 | - match: ^$ 16 | pop: true 17 | - match: ^ (STATUS:).+ 18 | captures: 19 | 1: comment.git-savvy.summary-header.title.status.rebase 20 | 21 | - match: ^( [┃│] )(.+)\n 22 | scope: meta.git-savvy.rebase-graph.conflict 23 | captures: 24 | 1: comment.git-savvy.rebase-graph.separator 25 | 2: keyword.other.name.git-savvy.rebase-conflict 26 | - match: " [┃│]" 27 | scope: comment.git-savvy.rebase-graph.separator 28 | - match: " [┻┴]" 29 | scope: comment.git-savvy.rebase-graph.end 30 | - match: ' [┳┬] (\()([0-9a-f]{7,40})(\))' 31 | scope: comment.git-savvy.rebase-graph.base 32 | captures: 33 | 1: punctuation.definition.git-savvy.rebase-graph.base 34 | 2: support.type.git-savvy.rebase.commit_hash 35 | 3: punctuation.definition.git-savvy.rebase-graph.base 36 | 37 | - match: ' ([▸ ]) ([·●]) ([0-9a-f]{7,40}) (.*)\n' 38 | scope: meta.git-savvy.rebase-graph.entry 39 | captures: 40 | 1: string.other.git-savvy.rebase.caret 41 | 2: comment.git-savvy.rebase-graph.commit-node 42 | 3: support.type.git-savvy.rebase.commit_hash 43 | 4: gitsavvy.gotosymbol 44 | - match: ' ([▸ ]) (✔) ([0-9a-f]{7,40}) (.*)\n' 45 | scope: meta.git-savvy.rebase-graph.entry 46 | captures: 47 | 1: string.other.git-savvy.rebase.caret 48 | 2: entity.name.function.git-savvy.success 49 | 3: support.type.git-savvy.rebase.commit_hash 50 | 4: gitsavvy.gotosymbol 51 | - match: ' ([▸ ]) ([✕✘]) ([0-9a-f]{7,40}) (.*)\n' 52 | scope: meta.git-savvy.rebase-graph.entry 53 | captures: 54 | 1: string.other.git-savvy.rebase.caret 55 | 2: string.other.git-savvy.conflict 56 | 3: support.type.git-savvy.rebase.commit_hash 57 | 4: gitsavvy.gotosymbol 58 | 59 | -------------------------------------------------------------------------------- /syntax/show_commit.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // same on all syntaxes 3 | "auto_indent": false, 4 | "detect_indentation": false, 5 | "draw_indent_guides": false, 6 | "draw_white_space": "None", 7 | "gutter": false, 8 | "line_numbers": false, 9 | "margin": 20, 10 | "rulers": [], 11 | "translate_tabs_to_spaces": false, 12 | "word_wrap": false, 13 | // syntax specific 14 | "match_brackets": false, 15 | "match_tags": false 16 | } 17 | -------------------------------------------------------------------------------- /syntax/show_commit.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: GitSavvy Commit 5 | hidden: true 6 | scope: git-savvy.commit 7 | contexts: 8 | main: 9 | - match: (?=^(commit|tag|From)) 10 | push: commit-header 11 | - match: (?=^diff\s) 12 | embed: "scope:git-savvy.diff" 13 | escape: (?=^commit \h{7,}) 14 | # Merge commits "just" start the diffstat without the "---" line 15 | - match: (?=^ \S) 16 | embed: "scope:git-savvy.diff" 17 | escape: (?=^(commit|From) \h{7,}) 18 | - match: ^---$\n? 19 | scope: meta.commit-header-and-stat-splitter 20 | embed: "scope:git-savvy.diff" 21 | escape: (?=^(commit|From) \h{7,}) 22 | - match: ^-- .* --$ 23 | scope: markup.deleted 24 | 25 | commit-header: 26 | - meta_scope: meta.commit-info.header 27 | - match: ^(commit|tag|From) (.+)$\n? 28 | comment: commit header 29 | scope: meta.commit-info.header.key-value 30 | captures: 31 | 1: string.other.commit-info.header.key.git-savvy 32 | 2: meta.commit-info.header.value.git-savvy meta.commit-info.header.sha.git-savvy 33 | 34 | - match: ^(Author|AuthorDate|Commit|CommitDate|Date|From|Merge|Tagger|TaggerDate)(:)(.+)$\n? 35 | comment: author and date info 36 | scope: meta.commit-info.header.key-value 37 | captures: 38 | 1: string.other.commit-info.header.key.git-savvy 39 | 2: string.other.commit-info.header.key.punctuation.git-savvy 40 | 3: meta.commit-info.header.value.git-savvy 41 | 42 | - match: ^(Subject)(:)\s(.+)$\n? 43 | comment: subject (in a patch) 44 | scope: meta.commit-info.header.key-value 45 | captures: 46 | 1: string.other.commit-info.header.key.git-savvy 47 | 2: string.other.commit-info.header.key.punctuation.git-savvy 48 | 3: meta.commit-info.header.value.git-savvy gitsavvy.gotosymbol meta.commit_message meta.subject.git.commit 49 | set: commit-subject 50 | 51 | - match: ^$ 52 | set: commit-message 53 | 54 | commit-message: 55 | - meta_scope: meta.commit_message 56 | - match: ^\s*(?=\S+) 57 | set: commit-subject 58 | 59 | commit-subject: 60 | - meta_content_scope: gitsavvy.gotosymbol meta.commit_message meta.subject.git.commit markup.heading.subject.git.commit 61 | - match: $ 62 | set: commit-message-body 63 | - include: make_commit.sublime-syntax#references 64 | 65 | commit-message-body: 66 | - meta_scope: meta.commit_message 67 | - match: (?=^commit \h{7,}) 68 | set: main 69 | - match: (?=^---$\n?) 70 | set: main 71 | - match: (?=^diff\s) 72 | set: main 73 | - match: (?=^ \S) 74 | set: main 75 | - include: make_commit.sublime-syntax#references 76 | -------------------------------------------------------------------------------- /syntax/show_commit_diffstat.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: GitSavvy Diffstat 5 | hidden: true 6 | scope: git-savvy.commit-diffstat 7 | contexts: 8 | main: 9 | - match: ^ (\S.+?) +\| +(\d+ |0)(\+*)(-*)$\n? 10 | comment: author and date info 11 | scope: meta.commit-info.diffstat.line 12 | captures: 13 | 1: meta.filename.diff 14 | 2: constant.numeric.lines-count.git-savvy 15 | 3: markup.inserted.git-savvy.add-block.content 16 | 4: markup.deleted.git-savvy.delete-block.content 17 | 18 | - match: (\d+) files? changed 19 | comment: Count files changed 20 | scope: meta.commit-info.files.changes 21 | captures: 22 | 1: constant.numeric.lines-count.git-savvy 23 | 24 | - match: (\d+) insertions?\(\+\) 25 | comment: Count of lines insertions 26 | scope: markup.inserted.git-savvy.add-block.content 27 | captures: 28 | 1: constant.numeric.lines-count.git-savvy 29 | 30 | - match: (\d+) deletions?\(-\) 31 | comment: Count of lines deletions 32 | scope: markup.deleted.git-savvy.delete-block.content 33 | captures: 34 | 1: constant.numeric.lines-count.git-savvy 35 | -------------------------------------------------------------------------------- /syntax/status.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // same on all syntaxes 3 | "auto_indent": false, 4 | "detect_indentation": false, 5 | "draw_indent_guides": false, 6 | "draw_white_space": "None", 7 | "gutter": false, 8 | "line_numbers": false, 9 | "margin": 20, 10 | "rulers": [], 11 | "translate_tabs_to_spaces": false, 12 | "word_wrap": false, 13 | "draw_unicode_white_space": "none", 14 | 15 | // syntax specific 16 | "gutter": true, 17 | "margin": -20, 18 | "highlight_line": true, 19 | "fold_buttons": true, 20 | "fade_fold_buttons": false, 21 | "match_brackets": false, 22 | "match_tags": false 23 | } 24 | -------------------------------------------------------------------------------- /syntax/tags.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // same on all syntaxes 3 | "auto_indent": false, 4 | "detect_indentation": false, 5 | "draw_indent_guides": false, 6 | "draw_white_space": "None", 7 | "gutter": false, 8 | "line_numbers": false, 9 | "margin": 20, 10 | "rulers": [], 11 | "translate_tabs_to_spaces": false, 12 | "word_wrap": false, 13 | 14 | // syntax specific 15 | "gutter": true, 16 | "margin": -20, 17 | "highlight_line": true, 18 | "fold_buttons": true, 19 | "fade_fold_buttons": false, 20 | "match_brackets": false, 21 | "match_tags": false 22 | } 23 | -------------------------------------------------------------------------------- /syntax/tags.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: GitSavvy Tags 5 | hidden: true 6 | scope: git-savvy.tags 7 | contexts: 8 | main: 9 | - include: "Packages/GitSavvy/syntax/dashboard.sublime-syntax" 10 | 11 | - match: ^ LOCAL:$ 12 | comment: local section 13 | scope: keyword.other.git-savvy.section-header.tags.local 14 | push: 15 | - meta_scope: meta.git-savvy.section.tags.local 16 | - include: section 17 | 18 | - match: ^ REMOTE (\()(.+)(\)):$ 19 | comment: remote section 20 | scope: keyword.other.git-savvy.section-header.tags.remote 21 | captures: 22 | 1: punctuation.definition.git-savvy.section-header.remote 23 | 2: constant.other.git-savvy.tags.remote-name 24 | 3: punctuation.definition.git-savvy.section-header.remote 25 | push: 26 | - meta_scope: meta.git-savvy.section.tags.remote 27 | - include: section 28 | 29 | section: 30 | - match: ^$ 31 | pop: true 32 | - match: '^ (?:(\*)|(!)| )([0-9a-f]{7,40}) (.+?)(?: +(.+))?\n$' 33 | scope: meta.git-savvy.tags.tag 34 | captures: 35 | 1: punctuation.tag-tag.new-tag.git-savvy 36 | 2: punctuation.tag-tag.changed-tag.git-savvy 37 | 3: constant.other.git-savvy.tags.sha1 38 | 4: meta.git-savvy.tag.name gitsavvy.gotosymbol 39 | 5: comment.git-savvy.tag.age 40 | -------------------------------------------------------------------------------- /syntax/test/syntax_test_blame.txt: -------------------------------------------------------------------------------- 1 | # SYNTAX TEST "Packages/GitSavvy/syntax/blame.sublime-syntax" 2 | 3 | update blame syntax format | 1 %YAML 1.2 4 | # ^ comment.block 5 | # ^ comment.block 6 | # ^ comment.block.git-savvy.splitter.vertical 7 | # ^ constant.numeric.line-number 8 | 9 | f483ab7eb511 | 2 --- 10 | # ^ comment.block.git-savvy.splitter.vertical 11 | # ^ constant.numeric.commit-hash 12 | 13 | gwenzek | 3 # http://www.sublimetext.com/docs/3/syntax.html 14 | # ^ comment.block.git-savvy.splitter.vertical 15 | # ^ entity.name.tag.git-savvy 16 | # ^ punctuation.definition.other.begin.git-savvy 17 | # ^^^^^^^^^^^^^^^^^^^^^^^^^^ string.other.mail.git-savvy 18 | # ^ punctuation.definition.other.end.git-savvy 19 | 20 | Jan 29, 2015 | 21 | # ^ comment.block.git-savvy.splitter.vertical 22 | # <- comment.block.git-savvy.commit-info 23 | # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ comment.block.git-savvy.commit-info 24 | 25 | Rename `base_command#BaseCommand` to ... | 4 from ..git_command import GitCommand 26 | # ^ comment.block.git-savvy.splitter.vertical 27 | # <- comment.block.git-savvy.commit-info 28 | # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ comment.block.git-savvy.commit-info 29 | # ^ constant.numeric.line-number 30 | # ^ git-savvy.blame 31 | 32 | -------------------------------------------- | ------------------------------------------ 33 | # ^ comment.block.git-savvy.splitter.horizontal.commit-info 34 | # ^ comment.block.git-savvy.splitter.vertical 35 | # ^ comment.block.git-savvy.splitter.horizontal.source 36 | -------------------------------------------------------------------------------- /syntax/test/syntax_test_branch.txt: -------------------------------------------------------------------------------- 1 | # SYNTAX TEST "Packages/GitSavvy/syntax/branch.sublime-syntax" 2 | 3 | LOCAL: 4 | # <- meta.git-savvy.status -meta.git-savvy.status-summary 5 | # ^ keyword.other 6 | ▸ aaf90ba develop 7 | # ^ punctuation.symbol.active-branch.git-savvy 8 | # ^^^^^^^ constant.other.git-savvy.branches.branch.sha1 9 | # ^^^^^^^ meta.git-savvy.branches.branch.name 10 | # ^^^^^^^ gitsavvy.gotosymbol 11 | ▸ aaf90ba develop (origin/develop) 12 | #^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.git-savvy.branches.branch.active-branch 13 | # <- meta.git-savvy.branches.branch.active-branch 14 | f21e6a8 old (origin/develop, ahead 13, behind 40) 15 | # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - meta.git-savvy.branches.branch.active-branch 16 | # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ comment.git-savvy.branches.branch.tracking-info 17 | b20264b iter-branches-dashboard-code 12 minutes ago 18 | # ^^^^^^^ constant.other.git-savvy.branches.branch.sha1 19 | # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.git-savvy.branches.branch.name gitsavvy.gotosymbol 20 | # ^^^^^^^^^^^^^^ comment.git-savvy.branches.branch.date 21 | fb8240d status-bar-updater (fork/status-bar-updater, gone) 22 | # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ comment.git-savvy.branches.branch.tracking-info 23 | # ^^^^ constant.git-savvy.upstream.gone 24 | fb8240d status-bar-updater (fork/status-bar-updater) 25 | # ^^^^^^^^^^^^^^^^^^^^^^^^^ comment.git-savvy.branches.branch.tracking-info 26 | 369ff34 fix-environment 6 years ago, (origin/fix-environment) 27 | # ^^^^^^^^^^^ comment.git-savvy.branches.branch.date 28 | # ^^^^^^^^^^^^^^^^^^^^^^^^ comment.git-savvy.branches.branch.tracking-info 29 | ▸ 7f561b6 master Wed 20:04 -0700, (origin/master) - desc 30 | # ^^^^^^^^^^^^^^^ comment.git-savvy.branches.branch.date 31 | # ^^^^^^^^^^^^^^ comment.git-savvy.branches.branch.tracking-info 32 | # ^^^^^^ comment.git-savvy.branches.branch.description keyword 33 | ▸ 7f561b6 master Wed 20:04 -0700 - desc 34 | # ^^^^^^^^^^^^^^^ comment.git-savvy.branches.branch.date 35 | # ^^^^^^ comment.git-savvy.branches.branch.description keyword 36 | 7f561b6 master (origin/master) - desc 37 | # ^^^^^^^^^^^^^^ comment.git-savvy.branches.branch.tracking-info 38 | # ^^^^^^^ comment.git-savvy.branches.branch.description keyword 39 | 7f561b6 master - desc 40 | # ^^^^^^ comment.git-savvy.branches.branch.description keyword 41 | 67df3f5 better-error-handling 12 months ago - desc 42 | # ^^^^^^^^^^^^^ comment.git-savvy.branches.branch.date 43 | # ^^^^^^ comment.git-savvy.branches.branch.description keyword 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /syntax/test/syntax_test_graph_pick_axe.txt: -------------------------------------------------------------------------------- 1 | # SYNTAX TEST "Packages/GitSavvy/syntax/graph.sublime-syntax" 2 | 3 | REPO: C:\Users\c-flo\AppData\Roaming\Sublime Text\Packages\GitSavvy 4 | [a]ll: false 5 | -S''' 6 | class gs_revert_commit(LogMixin, WindowCommand, GitCommand): 7 | # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ git-savvy.graph meta.prelude.git_savvy.graph comment.prelude.git_savvy.graph 8 | @on_worker 9 | # ^^^^^^^^^^^^^^^^ git-savvy.graph meta.prelude.git_savvy.graph comment.prelude.git_savvy.graph 10 | def do_action(self, commit_hash, **kwargs): 11 | # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ git-savvy.graph meta.prelude.git_savvy.graph comment.prelude.git_savvy.graph 12 | try: 13 | # ^^^^^^^^^^^^^^ git-savvy.graph meta.prelude.git_savvy.graph comment.prelude.git_savvy.graph 14 | self.git("revert", *(commit_hash if isinstance(commit_hash, list) else [commit_hash])) 15 | # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ git-savvy.graph meta.prelude.git_savvy.graph comment.prelude.git_savvy.graph 16 | finally: 17 | # ^^^^^^^^^^^^^^^^^^ git-savvy.graph meta.prelude.git_savvy.graph comment.prelude.git_savvy.graph 18 | util.view.refresh_gitsavvy(self.window.active_view(), refresh_sidebar=True) 19 | # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ git-savvy.graph meta.prelude.git_savvy.graph comment.prelude.git_savvy.graph 20 | ''' 21 | # ^^^ git-savvy.graph meta.prelude.git_savvy.graph comment.prelude.git_savvy.graph 22 | 23 | #< git-savvy.graph meta.prelude.git_savvy.graph 24 | ... 25 | # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ git-savvy.graph meta.content.git_savvy.graph 26 | ● 96c5f826 Allow to revert multiple commits in one invocation ​ Thu May 2 21:44, herr kaste 27 | # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ git-savvy.graph meta.content.git_savvy.graph 28 | -------------------------------------------------------------------------------- /syntax/test/syntax_test_rebase.txt: -------------------------------------------------------------------------------- 1 | # SYNTAX TEST "Packages/GitSavvy/syntax/rebase.sublime-syntax" 2 | 3 | REBASE: status-syntax-improvements --> master (a3b397c) 4 | # ^ comment.git-savvy.summary-header.title.rebase 5 | # ^ meta.git-savvy.rebase-summary 6 | STATUS: Ready. 7 | # ^ comment.git-savvy.summary-header.title.status.rebase 8 | # ^ meta.git-savvy.rebase-summary 9 | 10 | ┬ (a3b397c) 11 | # ^ comment.git-savvy.rebase-graph.base 12 | # ^ punctuation.definition.git-savvy.rebase-graph.base 13 | # ^ punctuation.definition.git-savvy.rebase-graph.base 14 | # ^ support.type.git-savvy.rebase.commit_hash 15 | │ 16 | # ^ comment.git-savvy.rebase-graph.separator 17 | ● 442483b Improved syntax denfinition for status dashboard 18 | # ^ comment.git-savvy.rebase-graph.commit-node 19 | # ^ support.type.git-savvy.rebase.commit_hash 20 | # ^ gitsavvy.gotosymbol 21 | ▸ ● 7c317a7 Tags and rebase use the dashboards base syntax 22 | # ^ string.other.git-savvy.rebase.caret 23 | # ^ comment.git-savvy.rebase-graph.commit-node 24 | # ^ support.type.git-savvy.rebase.commit_hash 25 | # ^ gitsavvy.gotosymbol 26 | ▸ ✔ e0e6c12 Improved tags syntax 27 | # ^ string.other.git-savvy.rebase.caret 28 | # ^ entity.name.function.git-savvy.success 29 | # ^ support.type.git-savvy.rebase.commit_hash 30 | # ^ gitsavvy.gotosymbol 31 | ▸ ✘ 53090ba removed code which is moved to dashboard.sublime-syntax 32 | # ^ string.other.git-savvy.rebase.caret 33 | # ^ string.other.git-savvy.conflict 34 | # ^ support.type.git-savvy.rebase.commit_hash 35 | # ^ gitsavvy.gotosymbol 36 | │ ! stuff.todo 37 | # ^ comment.git-savvy.rebase-graph.separator 38 | # ^ keyword.other.name.git-savvy.rebase-conflict 39 | # ^ keyword.other.name.git-savvy.rebase-conflict 40 | ┴ 41 | # ^ comment.git-savvy.rebase-graph.end 42 | 43 | 44 | ┳ (a3b397c) 45 | # ^ comment.git-savvy.rebase-graph.base 46 | # ^ punctuation.definition.git-savvy.rebase-graph.base 47 | # ^ punctuation.definition.git-savvy.rebase-graph.base 48 | # ^ support.type.git-savvy.rebase.commit_hash 49 | ┃ 50 | # ^ comment.git-savvy.rebase-graph.separator 51 | · 442483b Improved syntax denfinition for status dashboard 52 | # ^ comment.git-savvy.rebase-graph.commit-node 53 | # ^ support.type.git-savvy.rebase.commit_hash 54 | # ^ gitsavvy.gotosymbol 55 | ▸ · 7c317a7 Tags and rebase use the dashboards base syntax 56 | # ^ string.other.git-savvy.rebase.caret 57 | # ^ comment.git-savvy.rebase-graph.commit-node 58 | # ^ support.type.git-savvy.rebase.commit_hash 59 | # ^ gitsavvy.gotosymbol 60 | ▸ ✕ 53090ba removed code which is moved to dashboard.sublime-syntax 61 | # ^ string.other.git-savvy.rebase.caret 62 | # ^ string.other.git-savvy.conflict 63 | # ^ support.type.git-savvy.rebase.commit_hash 64 | # ^ gitsavvy.gotosymbol 65 | ┻ 66 | # ^ comment.git-savvy.rebase-graph.end 67 | -------------------------------------------------------------------------------- /syntax/test/syntax_test_show_commit.txt: -------------------------------------------------------------------------------- 1 | # SYNTAX TEST "Packages/GitSavvy/syntax/show_commit.sublime-syntax" 2 | 3 | commit 174ef25e0714b5187b4713091be2ea9bfd9d3384 4 | # ^ string.other.commit-info.header.key.git-savvy 5 | # ^ meta.commit-info.header.value.git-savvy 6 | # ^ meta.commit-info.header.sha.git-savvy 7 | # <- meta.commit-info.header.key-value 8 | Author: Simon 9 | # ^ string.other.commit-info.header.key.git-savvy 10 | # ^ string.other.commit-info.header.key.punctuation.git-savvy 11 | # ^ meta.commit-info.header.value.git-savvy 12 | # <- meta.commit-info.header.key-value 13 | Commit: Simon 14 | # ^ string.other.commit-info.header.key.git-savvy 15 | # ^ string.other.commit-info.header.key.punctuation.git-savvy 16 | # ^ meta.commit-info.header.value.git-savvy 17 | # <- meta.commit-info.header.key-value 18 | 19 | Improve: semver tags sorting when failure 20 | # <- -meta.commit-info.header.key-value 21 | -------------------------------------------------------------------------------- /syntax/test/syntax_test_tags.txt: -------------------------------------------------------------------------------- 1 | # SYNTAX TEST "Packages/GitSavvy/syntax/tags.sublime-syntax" 2 | 3 | LOCAL: 4 | # ^ keyword.other.git-savvy.section-header.tags.local 5 | 77dec1d 2.10.1 6 | # ^ constant.other.git-savvy.tags.sha1 7 | # ^ meta.git-savvy.tag.name gitsavvy.gotosymbol 8 | 9 | REMOTE (origin): 10 | # ^ keyword.other.git-savvy.section-header.tags.remote 11 | # ^ punctuation.definition.git-savvy.section-header.remote 12 | # ^ constant.other.git-savvy.tags.remote-name 13 | # ^ punctuation.definition.git-savvy.section-header.remote 14 | 77dec1d 2.10.1 15 | # ^ constant.other.git-savvy.tags.sha1 16 | # ^ meta.git-savvy.tag.name gitsavvy.gotosymbol 17 | --------------------------------------------------------------------------------