git-project 0.0.26__tar.gz → 0.0.27__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. {git_project-0.0.26 → git_project-0.0.27}/PKG-INFO +1 -1
  2. {git_project-0.0.26 → git_project-0.0.27}/_version.py +2 -2
  3. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/git.py +17 -8
  4. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/substitutable.py +16 -13
  5. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/test_support/common.py +2 -2
  6. {git_project-0.0.26 → git_project-0.0.27}/test.out +10 -9
  7. {git_project-0.0.26 → git_project-0.0.27}/tests/test_git.py +57 -3
  8. {git_project-0.0.26 → git_project-0.0.27}/tests/test_project.py +5 -2
  9. {git_project-0.0.26 → git_project-0.0.27}/tests/test_substitutable.py +100 -0
  10. {git_project-0.0.26 → git_project-0.0.27}/.git +0 -0
  11. {git_project-0.0.26 → git_project-0.0.27}/.gitignore +0 -0
  12. {git_project-0.0.26 → git_project-0.0.27}/CONTRIBUTORS +0 -0
  13. {git_project-0.0.26 → git_project-0.0.27}/COPYING +0 -0
  14. {git_project-0.0.26 → git_project-0.0.27}/MANIFEST.in +0 -0
  15. {git_project-0.0.26 → git_project-0.0.27}/build/lib/git_project/__init__.py +0 -0
  16. {git_project-0.0.26 → git_project-0.0.27}/build/lib/git_project/commandline.py +0 -0
  17. {git_project-0.0.26 → git_project-0.0.27}/build/lib/git_project/configobj.py +0 -0
  18. {git_project-0.0.26 → git_project-0.0.27}/build/lib/git_project/exception.py +0 -0
  19. {git_project-0.0.26 → git_project-0.0.27}/build/lib/git_project/git.py +0 -0
  20. {git_project-0.0.26 → git_project-0.0.27}/build/lib/git_project/gitproject.py +0 -0
  21. {git_project-0.0.26 → git_project-0.0.27}/build/lib/git_project/main.py +0 -0
  22. {git_project-0.0.26 → git_project-0.0.27}/build/lib/git_project/parsermanager.py +0 -0
  23. {git_project-0.0.26 → git_project-0.0.27}/build/lib/git_project/plugin.py +0 -0
  24. {git_project-0.0.26 → git_project-0.0.27}/build/lib/git_project/pluginmanager.py +0 -0
  25. {git_project-0.0.26 → git_project-0.0.27}/build/lib/git_project/project.py +0 -0
  26. {git_project-0.0.26 → git_project-0.0.27}/build/lib/git_project/runnable.py +0 -0
  27. {git_project-0.0.26 → git_project-0.0.27}/build/lib/git_project/scopedobj.py +0 -0
  28. {git_project-0.0.26 → git_project-0.0.27}/build/lib/git_project/shell.py +0 -0
  29. {git_project-0.0.26 → git_project-0.0.27}/build/lib/git_project/substitutable.py +0 -0
  30. {git_project-0.0.26 → git_project-0.0.27}/docs/Makefile +0 -0
  31. {git_project-0.0.26 → git_project-0.0.27}/docs/authors.rst +0 -0
  32. {git_project-0.0.26 → git_project-0.0.27}/docs/changelog.rst +0 -0
  33. {git_project-0.0.26 → git_project-0.0.27}/docs/conf.py +0 -0
  34. {git_project-0.0.26 → git_project-0.0.27}/docs/index.rst +0 -0
  35. {git_project-0.0.26 → git_project-0.0.27}/docs/intro.rst +0 -0
  36. {git_project-0.0.26 → git_project-0.0.27}/docs/license.rst +0 -0
  37. {git_project-0.0.26 → git_project-0.0.27}/docs/make.bat +0 -0
  38. {git_project-0.0.26 → git_project-0.0.27}/pyproject.toml +0 -0
  39. {git_project-0.0.26 → git_project-0.0.27}/requirements.txt +0 -0
  40. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/__init__.py +0 -0
  41. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/commandline.py +0 -0
  42. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/configobj.py +0 -0
  43. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/exception.py +0 -0
  44. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/gitproject.py +0 -0
  45. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/main.py +0 -0
  46. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/parsermanager.py +0 -0
  47. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/plugin.py +0 -0
  48. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/pluginmanager.py +0 -0
  49. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/project.py +0 -0
  50. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/runnable.py +0 -0
  51. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/scopedobj.py +0 -0
  52. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/shell.py +0 -0
  53. {git_project-0.0.26 → git_project-0.0.27}/src/git_project/test_support/__init__.py +0 -0
  54. {git_project-0.0.26 → git_project-0.0.27}/tests/conftest.py +0 -0
  55. {git_project-0.0.26 → git_project-0.0.27}/tests/test_confobj.py +0 -0
  56. {git_project-0.0.26 → git_project-0.0.27}/tests/test_gitproject.py +0 -0
  57. {git_project-0.0.26 → git_project-0.0.27}/tests/test_main.py +0 -0
  58. {git_project-0.0.26 → git_project-0.0.27}/tests/test_runnable.py +0 -0
  59. {git_project-0.0.26 → git_project-0.0.27}/tests/test_scopedobj.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-project
3
- Version: 0.0.26
3
+ Version: 0.0.27
4
4
  Summary: The extensible stupid project manager
5
5
  Author-email: "David A. Greene" <dag@obbligato.org>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.0.26'
16
- __version_tuple__ = version_tuple = (0, 0, 26)
15
+ __version__ = version = '0.0.27'
16
+ __version_tuple__ = version_tuple = (0, 0, 27)
@@ -396,7 +396,10 @@ class Git(object):
396
396
 
397
397
  # Repository-wide info
398
398
  def __init__(self):
399
- repo_path = pygit2.discover_repository(Path.cwd())
399
+ self.reinit(Path.cwd())
400
+
401
+ def reinit(self, gitdir: Path):
402
+ repo_path = pygit2.discover_repository(gitdir)
400
403
  if repo_path:
401
404
  self._repo = pygit2.Repository(repo_path)
402
405
  self._config = self.Config(self, self._repo.config)
@@ -461,16 +464,18 @@ class Git(object):
461
464
  return self._repo.path
462
465
 
463
466
  def get_git_common_dir(self):
464
- """Get the GIT_COMMON_DIR directory.
467
+ """Get the GIT_COMMON_DIR directory."""
468
+ if not self.get_current_worktree():
469
+ return self.get_gitdir()
465
470
 
466
- NOTE: This assumes that worktree paths are in .git/worktrees/<name>
471
+ worktree_gitdir = Path(self._repo.path)
467
472
 
468
- """
469
- gitdir = Path(self._repo.path)
470
- while gitdir.name != '.git':
471
- gitdir = gitdir.parent
473
+ commondir_filename = worktree_gitdir / 'commondir'
472
474
 
473
- return str(gitdir)
475
+ with open(commondir_filename, 'r') as commondir_file:
476
+ commondir = commondir_file.read().strip()
477
+
478
+ return commondir
474
479
 
475
480
  def get_current_worktree(self):
476
481
  """Return the name of the current worktree or None if we are not in a
@@ -556,6 +561,10 @@ class Git(object):
556
561
 
557
562
  # Operations on remotes
558
563
 
564
+ def get_remote_url(self, remote):
565
+ """Get the URL for the given remote."""
566
+ return self._repo.remotes[remote].url
567
+
559
568
  def set_remote_fetch_refspecs(self, remote, refspecs):
560
569
  """Set the refspec list for the given remote to refspecs."""
561
570
  if isinstance(refspecs, str):
@@ -64,28 +64,18 @@ class SubstitutableConfigObject(ConfigObject):
64
64
 
65
65
  # Substitute for project-global values.
66
66
  for key, value in project.iteritems():
67
- if key == 'path':
68
- found_path = True
69
67
  if key == self.get_subsection():
70
68
  value = self.get_ident()
71
69
  formats[key] = value
72
70
 
73
71
  # Substitute for values in self.
74
72
  for key, value in self.iteritems():
75
- if key == 'path':
76
- found_path = True
77
73
  formats[key] = value
78
74
 
79
75
  formats['project'] = project.get_section()
80
- formats['gitdir'] = git.get_gitdir()
81
- formats['git_common_dir'] = git.get_git_common_dir()
82
-
83
- if not found_path:
84
- # We haven't found a worktree or other construct to give us a path,
85
- # so do a mildly expensive thing to get the path of the curernt
86
- # working copy.
87
- path = git.get_working_copy_root()
88
- formats['path'] = path
76
+ formats['gitdir'] = str(git.get_gitdir())
77
+ formats['git_common_dir'] = str(git.get_git_common_dir())
78
+ formats['git_workdir'] = str(git.get_working_copy_root())
89
79
 
90
80
  current_branch = git.get_current_branch()
91
81
  if not current_branch:
@@ -107,6 +97,19 @@ class SubstitutableConfigObject(ConfigObject):
107
97
 
108
98
  formats['branch'] = current_branch
109
99
 
100
+ # Make sure substitutions don't reference themselves, to avoid an
101
+ # infinite loop substituting.
102
+ #
103
+ # NOTE: This won't catch mutually-recursive situations like:
104
+ #
105
+ # path: {dir}/foo
106
+ # dir: {path}/bar
107
+ for key, value in formats.items():
108
+ if f'{{{key}}}' in value:
109
+ raise RuntimeError(
110
+ f'Recursive substitution: {key} is in {value}'
111
+ )
112
+
110
113
  def try_format(string, formats):
111
114
  return eval(f"f'{string}'", formats)
112
115
 
@@ -323,9 +323,9 @@ def init_clone(url, path):
323
323
 
324
324
  remote_only_commit = repo.revparse_single('refs/remotes/origin/remote_only')
325
325
 
326
- merged_remote_only_branch = repo.branches.create('merged_remote_only', remote_only_commit)
326
+ pushed_remote_only_branch = repo.branches.create('pushed_remote_only', remote_only_commit)
327
327
 
328
- # -------merged_remote, origin/old_master--origin/master, pushed_indirectly--origin/remote_only, merged_remote_only
328
+ # -------merged_remote, origin/old_master--origin/master, pushed_indirectly--origin/remote_only, pushed_remote_only
329
329
  # | \
330
330
  # | `---master, merged_local--unmerged
331
331
  # \
@@ -3,15 +3,16 @@ platform darwin -- Python 3.13.2, pytest-8.3.4, pluggy-1.5.0
3
3
  rootdir: /Users/dag/projects/git-project/master
4
4
  configfile: pyproject.toml
5
5
  plugins: console-scripts-1.4.1
6
- collected 106 items
6
+ collected 111 items
7
7
 
8
- tests/test_confobj.py ............ [ 11%]
9
- tests/test_git.py ..................................................... [ 61%]
8
+ tests/test_confobj.py ............ [ 10%]
9
+ tests/test_git.py ...................................................... [ 59%]
10
+ .. [ 61%]
10
11
  tests/test_gitproject.py .. [ 63%]
11
- tests/test_main.py . [ 64%]
12
- tests/test_project.py ........... [ 74%]
13
- tests/test_runnable.py ........ [ 82%]
14
- tests/test_scopedobj.py ..... [ 86%]
15
- tests/test_substitutable.py .............. [100%]
12
+ tests/test_main.py . [ 63%]
13
+ tests/test_project.py ........... [ 73%]
14
+ tests/test_runnable.py ........ [ 81%]
15
+ tests/test_scopedobj.py ..... [ 85%]
16
+ tests/test_substitutable.py ................ [100%]
16
17
 
17
- ============================= 106 passed in 31.62s =============================
18
+ ============================= 111 passed in 32.41s =============================
@@ -391,10 +391,10 @@ def test_git_iterrefnames(reset_directory, local_repository):
391
391
  ['refs/heads/master',
392
392
  'refs/heads/merged_local',
393
393
  'refs/heads/merged_remote',
394
- 'refs/heads/merged_remote_only',
395
394
  'refs/heads/notpushed',
396
395
  'refs/heads/pushed',
397
396
  'refs/heads/pushed_indirectly',
397
+ 'refs/heads/pushed_remote_only',
398
398
  'refs/heads/unmerged'])
399
399
 
400
400
  def test_git_iterrefnames_str(reset_directory, local_repository):
@@ -408,10 +408,10 @@ def test_git_iterrefnames_str(reset_directory, local_repository):
408
408
  ['refs/heads/master',
409
409
  'refs/heads/merged_local',
410
410
  'refs/heads/merged_remote',
411
- 'refs/heads/merged_remote_only',
412
411
  'refs/heads/notpushed',
413
412
  'refs/heads/pushed',
414
413
  'refs/heads/pushed_indirectly',
414
+ 'refs/heads/pushed_remote_only',
415
415
  'refs/heads/unmerged'])
416
416
 
417
417
  def test_git_create_branch(reset_directory, local_repository):
@@ -668,10 +668,10 @@ def test_git_iterbranches(reset_directory, git):
668
668
  assert branches == {'master',
669
669
  'merged_local',
670
670
  'merged_remote',
671
- 'merged_remote_only',
672
671
  'notpushed',
673
672
  'pushed',
674
673
  'pushed_indirectly',
674
+ 'pushed_remote_only',
675
675
  'unmerged',
676
676
  'origin/HEAD',
677
677
  'origin/master',
@@ -820,3 +820,57 @@ def test_git_worktree_get_current_worktree(reset_directory, local_repository):
820
820
  wtgit = git_project.Git()
821
821
 
822
822
  assert wtgit.get_current_worktree() == 'test-wt'
823
+
824
+ def test_git_get_remote_url(reset_directory, local_repository, remote_repository):
825
+ os.chdir(local_repository.path)
826
+
827
+ git = git_project.Git()
828
+
829
+ assert git.has_repo()
830
+
831
+ url = git.get_remote_url('origin')
832
+
833
+ assert url == remote_repository.path
834
+
835
+ def test_git_reinit(local_repository):
836
+ repo_parent = Path(local_repository.path).parent
837
+
838
+ os.chdir(local_repository.path)
839
+
840
+ git = git_project.Git()
841
+
842
+ assert git.has_repo()
843
+ assert Path(git.get_gitdir()) == Path(local_repository.path)
844
+
845
+ new_hidden_dir = '.test.git'
846
+
847
+ gitdir = Path(git.get_gitdir())
848
+ newgitdir = repo_parent / new_hidden_dir
849
+
850
+ gitdir.rename(newgitdir)
851
+
852
+ git.reinit(newgitdir)
853
+
854
+ assert git.has_repo()
855
+ assert Path(git.get_gitdir()) == repo_parent / new_hidden_dir
856
+
857
+ def test_git_get_git_common_dir_renamed(reset_directory, local_repository):
858
+ repo_parent = Path(local_repository.path).parent
859
+
860
+ os.chdir(local_repository.path)
861
+
862
+ git = git_project.Git()
863
+
864
+ assert git.has_repo()
865
+
866
+ new_hidden_dir = '.test.git'
867
+
868
+ gitdir = Path(git.get_gitdir())
869
+ newgitdir = repo_parent / new_hidden_dir
870
+
871
+ gitdir.rename(newgitdir)
872
+
873
+ git.reinit(newgitdir)
874
+
875
+ assert git.has_repo()
876
+ assert Path(git.get_git_common_dir()).name == new_hidden_dir
@@ -77,6 +77,7 @@ def test_project_add_branch(project):
77
77
  assert branches == {'master', 'project', 'remote_only'}
78
78
 
79
79
  def test_project_iterrefnames(project):
80
+ print(f'In {Path.cwd()}')
80
81
  project.add_branch('pushed')
81
82
 
82
83
  branches = {branch for branch in project.iterbranches()}
@@ -93,6 +94,7 @@ def test_project_iterrefnames(project):
93
94
  'refs/remotes/origin/master',
94
95
  'refs/heads/pushed',
95
96
  'refs/heads/pushed_indirectly',
97
+ 'refs/heads/pushed_remote_only',
96
98
  'refs/remotes/origin/pushed',
97
99
  'refs/remotes/origin/remote_only'}
98
100
 
@@ -104,7 +106,8 @@ def test_project_branch_is_merged(project):
104
106
  assert project.branch_is_merged('merged_local')
105
107
  assert not project.branch_is_merged('unmerged')
106
108
  assert project.branch_is_merged('merged_remote')
107
- assert project.branch_is_merged('merged_remote_only')
109
+ # FIXME: Is not really merged!
110
+ assert project.branch_is_merged('pushed_remote_only')
108
111
 
109
112
  def test_project_branch_is_pushed(project):
110
113
  assert not project.branch_is_pushed('master')
@@ -113,7 +116,7 @@ def test_project_branch_is_pushed(project):
113
116
  assert project.branch_is_pushed('merged_remote')
114
117
  assert not project.branch_is_pushed('merged_local')
115
118
  assert not project.branch_is_pushed('unmerged')
116
- assert project.branch_is_pushed('merged_remote_only')
119
+ assert project.branch_is_pushed('pushed_remote_only')
117
120
 
118
121
  def test_project_prune_branch(reset_directory,
119
122
  remote_repository,
@@ -25,6 +25,7 @@ from git_project.test_support import check_config_file
25
25
 
26
26
  import os
27
27
  from pathlib import Path
28
+ import pytest
28
29
  import shutil
29
30
 
30
31
  class MySubstitutable(git_project.SubstitutableConfigObject):
@@ -402,3 +403,102 @@ def test_substitutable_substitute_fstring(reset_directory, git):
402
403
  substitutable.command)
403
404
 
404
405
  assert command == f'cd {project.builddir}/{current_branch.replace("imerge/", "", 1)} && make {project.target}'
406
+
407
+ def test_substitutable_substitute_recursive(reset_directory, git):
408
+ class MyProject(git_project.ScopedConfigObject):
409
+ def __init__(self):
410
+ super().__init__(git,
411
+ 'project',
412
+ None,
413
+ 'myproject',
414
+ builddir='/path/to/{builddir}',
415
+ target='debug')
416
+
417
+ substitutable = MySubstitutable.get(git, 'project', 'test')
418
+
419
+ project = MyProject()
420
+
421
+ with pytest.raises(Exception) as e:
422
+ command = substitutable.substitute_value(git,
423
+ project,
424
+ substitutable.command)
425
+
426
+ def test_substitutable_substitute_project(reset_directory, git):
427
+ class MyProject(git_project.ScopedConfigObject):
428
+ def __init__(self):
429
+ super().__init__(git,
430
+ 'project',
431
+ None,
432
+ 'myproject',
433
+ builddir='/path/to/build/{project}',
434
+ target='debug')
435
+
436
+ substitutable = MySubstitutable.get(git, 'project', 'test')
437
+
438
+ project = MyProject()
439
+
440
+ command = substitutable.substitute_value(git,
441
+ project,
442
+ substitutable.command)
443
+
444
+ assert command == f'cd /path/to/build/{project.get_section()}/{git.get_current_branch()} && make {project.target}'
445
+
446
+ def test_substitutable_substitute_gitdir(reset_directory, git):
447
+ class MyProject(git_project.ScopedConfigObject):
448
+ def __init__(self):
449
+ super().__init__(git,
450
+ 'project',
451
+ None,
452
+ 'myproject',
453
+ builddir='{gitdir}/../path/to/build',
454
+ target='debug')
455
+
456
+ substitutable = MySubstitutable.get(git, 'project', 'test')
457
+
458
+ project = MyProject()
459
+
460
+ command = substitutable.substitute_value(git,
461
+ project,
462
+ substitutable.command)
463
+
464
+ assert command == f'cd {git.get_gitdir()}/../path/to/build/{git.get_current_branch()} && make {project.target}'
465
+
466
+ def test_substitutable_substitute_git_common_dir(reset_directory, git):
467
+ class MyProject(git_project.ScopedConfigObject):
468
+ def __init__(self):
469
+ super().__init__(git,
470
+ 'project',
471
+ None,
472
+ 'myproject',
473
+ builddir='{git_common_dir}/../path/to/build',
474
+ target='debug')
475
+
476
+ substitutable = MySubstitutable.get(git, 'project', 'test')
477
+
478
+ project = MyProject()
479
+
480
+ command = substitutable.substitute_value(git,
481
+ project,
482
+ substitutable.command)
483
+
484
+ assert command == f'cd {git.get_git_common_dir()}/../path/to/build/{git.get_current_branch()} && make {project.target}'
485
+
486
+ def test_substitutable_substitute_git_workdir(reset_directory, git):
487
+ class MyProject(git_project.ScopedConfigObject):
488
+ def __init__(self):
489
+ super().__init__(git,
490
+ 'project',
491
+ None,
492
+ 'myproject',
493
+ builddir='{git_workdir}/path/to/build',
494
+ target='debug')
495
+
496
+ substitutable = MySubstitutable.get(git, 'project', 'test')
497
+
498
+ project = MyProject()
499
+
500
+ command = substitutable.substitute_value(git,
501
+ project,
502
+ substitutable.command)
503
+
504
+ assert command == f'cd {git.get_working_copy_root()}/path/to/build/{git.get_current_branch()} && make {project.target}'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes