Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,13 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Run tests
run: python -m unittest discover -s tests -v

windows-paths:
runs-on: windows-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.8'
- name: Run tests (including subst drive)
run: python -m unittest discover -s tests -v
22 changes: 18 additions & 4 deletions sublime_github.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,11 +390,25 @@ def done_remote(self, result):
# Get the repo's explicit toplevel path
def done_toplevel(self, result):
self.toplevel_path = result.strip()
# get file path within repo
# Preserve the opened filename (including symlinks) whenever its path is already relative to Git's root.
absolute_path = self.view.file_name()
# self.view.file_name() contains backslash on Windows instead of forwardslash
absolute_path = absolute_path.replace('\\', '/')
relative_path = "/" + os.path.relpath(absolute_path, self.toplevel_path).replace('\\', '/')
try:
relative_path = os.path.relpath(absolute_path, self.toplevel_path)
except ValueError: # Different drives, e.g. a Windows subst drive vs Git's physical path.
relative_path = None
if relative_path is None or relative_path == os.pardir or relative_path.startswith(os.pardir + os.sep):
# Resolve only the parent directory so a symlinked file keeps its name in the GitHub URL.
parent = os.path.realpath(os.path.dirname(absolute_path))
root = os.path.realpath(self.toplevel_path)
try:
relative_path = os.path.relpath(os.path.join(parent, os.path.basename(absolute_path)), root)
except ValueError:
sublime.error_message("Cannot locate this file within the Git repository.")
return
if relative_path == os.pardir or relative_path.startswith(os.pardir + os.sep):
sublime.error_message("This file is outside the Git repository.")
return
relative_path = "/" + relative_path.replace('\\', '/')

line_nums = ""
if self.allows_line_highlights:
Expand Down
61 changes: 61 additions & 0 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,67 @@ def test_git_helper_runs_in_file_directory_without_changing_process_cwd(self):
self.assertEqual([os.path.realpath(path) for path in results], [os.path.realpath(directory)])
self.assertEqual(os.getcwd(), original_cwd)

@unittest.skipIf(os.name == "nt", "creating symlinks requires Windows developer permissions")
def test_repository_link_keeps_symlink_filename(self):
with tempfile.TemporaryDirectory() as directory:
repo = Path(os.path.realpath(directory)) / "repo"
repo.mkdir()
(repo / "target.py").touch()
link = repo / "link.py"
link.symlink_to("target.py")
command = self.plugin.OpenRemoteUrlCommand()
command.view = types.SimpleNamespace(file_name=lambda: str(link), sel=lambda: [])
command.repo_url = "https://github.com/Giftly/China"
command.remote_branch = "main"
command.settings = types.SimpleNamespace(get=lambda key: None)
command.done_toplevel(str(repo))
self.assertEqual(self.opened_url, "https://github.com/Giftly/China/blob/main/link.py")

@unittest.skipIf(os.name == "nt", "creating symlinks requires Windows developer permissions")
def test_repository_link_resolves_directory_alias_but_keeps_file_symlink(self):
with tempfile.TemporaryDirectory() as directory:
directory = os.path.realpath(directory)
repo = Path(directory) / "repo"
repo.mkdir()
(repo / "target.py").touch()
(repo / "link.py").symlink_to("target.py")
alias = Path(directory) / "alias"
alias.symlink_to(repo, target_is_directory=True)
command = self.plugin.OpenRemoteUrlCommand()
command.view = types.SimpleNamespace(file_name=lambda: str(alias / "link.py"), sel=lambda: [])
command.repo_url = "https://github.com/Giftly/China"
command.remote_branch = "main"
command.settings = types.SimpleNamespace(get=lambda key: None)
command.done_toplevel(str(repo))
self.assertEqual(self.opened_url, "https://github.com/Giftly/China/blob/main/link.py")

@unittest.skipUnless(os.name == "nt", "requires Windows subst drives")
def test_repository_link_on_subst_drive(self):
with tempfile.TemporaryDirectory() as directory:
repo = Path(directory) / "repo"
repo.mkdir()
(repo / "file.py").touch()
drive = next(letter for letter in "ZYXWVUTSRQPONMLKJ" if not os.path.exists(letter + ":\\"))
subprocess.run(["subst", drive + ":", directory], check=True)
try:
mapped_repo = drive + ":\\repo"
subprocess.run(["git", "init", "-q", mapped_repo], check=True)
git_root = subprocess.check_output(
["git", "-C", mapped_repo, "rev-parse", "--show-toplevel"], text=True,
).strip()
command = self.plugin.OpenRemoteUrlCommand()
command.view = types.SimpleNamespace(file_name=lambda: mapped_repo + "\\file.py", sel=lambda: [])
command.repo_url = "https://github.com/Giftly/China"
command.remote_branch = "main"
command.settings = types.SimpleNamespace(get=lambda key: None)
command.done_toplevel(git_root)
self.assertEqual(self.opened_url, "https://github.com/Giftly/China/blob/main/file.py")
# Also exercise the physical path Git may return when using subst.
command.done_toplevel(str(repo))
self.assertEqual(self.opened_url, "https://github.com/Giftly/China/blob/main/file.py")
finally:
subprocess.run(["subst", drive + ":", "/D"], check=True)

def test_view_and_blame_detect_default_branch_and_include_selected_lines(self):
class Region:
def begin(self): return 9
Expand Down
Loading