git-project 0.0.26__tar.gz → 0.0.28__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 (64) hide show
  1. git_project-0.0.28/.#pyproject.toml +1 -0
  2. git_project-0.0.28/.git +1 -0
  3. {git_project-0.0.26 → git_project-0.0.28}/PKG-INFO +1 -1
  4. {git_project-0.0.26 → git_project-0.0.28}/_version.py +9 -4
  5. git_project-0.0.28/git_project-0.0.28.dev1+g2184a37-py3-none-any.whl +0 -0
  6. git_project-0.0.28/git_project-0.0.28.dev1+g2184a37.tar.gz +0 -0
  7. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/git.py +17 -8
  8. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/substitutable.py +29 -13
  9. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/test_support/common.py +2 -2
  10. git_project-0.0.28/test.out +131 -0
  11. {git_project-0.0.26 → git_project-0.0.28}/tests/test_git.py +57 -3
  12. {git_project-0.0.26 → git_project-0.0.28}/tests/test_project.py +5 -2
  13. {git_project-0.0.26 → git_project-0.0.28}/tests/test_substitutable.py +140 -0
  14. git_project-0.0.26/.git +0 -1
  15. git_project-0.0.26/build/lib/git_project/__init__.py +0 -33
  16. git_project-0.0.26/build/lib/git_project/commandline.py +0 -164
  17. git_project-0.0.26/build/lib/git_project/configobj.py +0 -300
  18. git_project-0.0.26/build/lib/git_project/exception.py +0 -30
  19. git_project-0.0.26/build/lib/git_project/git.py +0 -873
  20. git_project-0.0.26/build/lib/git_project/gitproject.py +0 -61
  21. git_project-0.0.26/build/lib/git_project/main.py +0 -63
  22. git_project-0.0.26/build/lib/git_project/parsermanager.py +0 -360
  23. git_project-0.0.26/build/lib/git_project/plugin.py +0 -118
  24. git_project-0.0.26/build/lib/git_project/pluginmanager.py +0 -54
  25. git_project-0.0.26/build/lib/git_project/project.py +0 -197
  26. git_project-0.0.26/build/lib/git_project/runnable.py +0 -71
  27. git_project-0.0.26/build/lib/git_project/scopedobj.py +0 -207
  28. git_project-0.0.26/build/lib/git_project/shell.py +0 -119
  29. git_project-0.0.26/build/lib/git_project/substitutable.py +0 -139
  30. git_project-0.0.26/test.out +0 -17
  31. {git_project-0.0.26 → git_project-0.0.28}/.gitignore +0 -0
  32. {git_project-0.0.26 → git_project-0.0.28}/CONTRIBUTORS +0 -0
  33. {git_project-0.0.26 → git_project-0.0.28}/COPYING +0 -0
  34. {git_project-0.0.26 → git_project-0.0.28}/MANIFEST.in +0 -0
  35. {git_project-0.0.26 → git_project-0.0.28}/docs/Makefile +0 -0
  36. {git_project-0.0.26 → git_project-0.0.28}/docs/authors.rst +0 -0
  37. {git_project-0.0.26 → git_project-0.0.28}/docs/changelog.rst +0 -0
  38. {git_project-0.0.26 → git_project-0.0.28}/docs/conf.py +0 -0
  39. {git_project-0.0.26 → git_project-0.0.28}/docs/index.rst +0 -0
  40. {git_project-0.0.26 → git_project-0.0.28}/docs/intro.rst +0 -0
  41. {git_project-0.0.26 → git_project-0.0.28}/docs/license.rst +0 -0
  42. {git_project-0.0.26 → git_project-0.0.28}/docs/make.bat +0 -0
  43. {git_project-0.0.26 → git_project-0.0.28}/pyproject.toml +0 -0
  44. {git_project-0.0.26 → git_project-0.0.28}/requirements.txt +0 -0
  45. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/__init__.py +0 -0
  46. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/commandline.py +0 -0
  47. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/configobj.py +0 -0
  48. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/exception.py +0 -0
  49. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/gitproject.py +0 -0
  50. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/main.py +0 -0
  51. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/parsermanager.py +0 -0
  52. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/plugin.py +0 -0
  53. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/pluginmanager.py +0 -0
  54. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/project.py +0 -0
  55. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/runnable.py +0 -0
  56. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/scopedobj.py +0 -0
  57. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/shell.py +0 -0
  58. {git_project-0.0.26 → git_project-0.0.28}/src/git_project/test_support/__init__.py +0 -0
  59. {git_project-0.0.26 → git_project-0.0.28}/tests/conftest.py +0 -0
  60. {git_project-0.0.26 → git_project-0.0.28}/tests/test_confobj.py +0 -0
  61. {git_project-0.0.26 → git_project-0.0.28}/tests/test_gitproject.py +0 -0
  62. {git_project-0.0.26 → git_project-0.0.28}/tests/test_main.py +0 -0
  63. {git_project-0.0.26 → git_project-0.0.28}/tests/test_runnable.py +0 -0
  64. {git_project-0.0.26 → git_project-0.0.28}/tests/test_scopedobj.py +0 -0
@@ -0,0 +1 @@
1
+ dag@basie.2617:1746908671
@@ -0,0 +1 @@
1
+ gitdir: /home/dag/src/git-project/.git/worktrees/dev/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-project
3
- Version: 0.0.26
3
+ Version: 0.0.28
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
@@ -1,8 +1,13 @@
1
- # file generated by setuptools_scm
1
+ # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
3
6
  TYPE_CHECKING = False
4
7
  if TYPE_CHECKING:
5
- from typing import Tuple, Union
8
+ from typing import Tuple
9
+ from typing import Union
10
+
6
11
  VERSION_TUPLE = Tuple[Union[int, str], ...]
7
12
  else:
8
13
  VERSION_TUPLE = object
@@ -12,5 +17,5 @@ __version__: str
12
17
  __version_tuple__: VERSION_TUPLE
13
18
  version_tuple: VERSION_TUPLE
14
19
 
15
- __version__ = version = '0.0.26'
16
- __version_tuple__ = version_tuple = (0, 0, 26)
20
+ __version__ = version = '0.0.28'
21
+ __version_tuple__ = version_tuple = (0, 0, 28)
@@ -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
 
@@ -116,7 +119,17 @@ class SubstitutableConfigObject(ConfigObject):
116
119
  value = scope.get_ident()
117
120
  formats[key] = value
118
121
 
122
+ # Allow escaping braces by surrounding each brace with braces. This is
123
+ # not valid f-string syntax and is unlikely to be used in ordinary
124
+ # config values. Replace with an equally unlikely sequence, then
125
+ # substitute on that string.
126
+
127
+ escaped_braces = False
119
128
  while True:
129
+ escaped_braces = escaped_braces or '{{}' in string or '{}}' in string
130
+ if escaped_braces:
131
+ string = string.replace('{{}', '[[[')
132
+ string = string.replace('{}}', ']]]')
120
133
  try:
121
134
  newstring = try_format(string, formats)
122
135
  except KeyError as exception:
@@ -137,6 +150,9 @@ class SubstitutableConfigObject(ConfigObject):
137
150
  changed = False if newstring == string else True
138
151
  string = newstring
139
152
  if not changed:
153
+ if escaped_braces:
154
+ string = string.replace('[[[', '{')
155
+ string = string.replace(']]]', '}')
140
156
  break
141
157
 
142
158
  return string
@@ -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
  # \
@@ -0,0 +1,131 @@
1
+ ============================= test session starts ==============================
2
+ platform linux -- Python 3.11.9, pytest-8.2.2, pluggy-1.5.0
3
+ rootdir: /home/dag/src/git-project/dev
4
+ configfile: pyproject.toml
5
+ plugins: anyio-4.3.0, console-scripts-1.4.1
6
+ collected 106 items
7
+
8
+ tests/test_confobj.py ............ [ 11%]
9
+ tests/test_git.py .......................FF............................ [ 61%]
10
+ 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%]
16
+
17
+ =================================== FAILURES ===================================
18
+ ________________________________ test_git_clone ________________________________
19
+
20
+ reset_directory = None
21
+ remote_repository = pygit2.Repository('/tmp/pytest-of-dag/pytest-14/local_remote_test_git_clone.git0/')
22
+ tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x7ff199ba1e50>, _basetemp=PosixPath('/tmp/pytest-of-dag/pytest-14'), _retention_count=3, _retention_policy='all')
23
+
24
+ def test_git_clone(reset_directory, remote_repository, tmp_path_factory):
25
+ path = tmp_path_factory.mktemp('clone-workdir')
26
+
27
+ os.chdir(path)
28
+
29
+ git = git_project.Git()
30
+
31
+ remote_url = 'file://' + remote_repository.path
32
+
33
+ > clone_path = git.clone(remote_url)
34
+
35
+ /home/dag/src/git-project/dev/tests/test_git.py:441:
36
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
37
+ /home/dag/src/git-project/dev/src/git_project/git.py:744: in clone
38
+ self._repo = pygit2.clone_repository(url, target_path, bare, callbacks=callbacks)
39
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/__init__.py:217: in clone_repository
40
+ payload.check_error(err)
41
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/callbacks.py:93: in check_error
42
+ raise self._stored_exception
43
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/callbacks.py:424: in wrapper
44
+ return f(*args)
45
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/callbacks.py:552: in _transfer_progress_cb
46
+ transfer_progress(TransferProgress(stats_ptr))
47
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
48
+
49
+ self = <git_project.git.Git.RemoteCallbacks object at 0x7ff199a06950>
50
+ stats = <pygit2.remotes.TransferProgress object at 0x7ff199a9f790>
51
+
52
+ def transfer_progress(self, stats):
53
+ if not self.started_transfer:
54
+ self.started_transfer = True
55
+ print(f'Receiving objects ({stats.total_objects})...')
56
+ > self.progress.start(max_value=stats.total_objects)
57
+ E TypeError: ProgressBar.start() got an unexpected keyword argument 'max_value'
58
+
59
+ /home/dag/src/git-project/dev/src/git_project/git.py:77: TypeError
60
+ ----------------------------- Captured stdout call -----------------------------
61
+ Remote: Counting objects 1
62
+ Remote: Counting objects 9
63
+ Remote: Counting objects 14
64
+ Remote: Counting objects 14
65
+
66
+ Remote: Compressing objects: 0% (0/14)
67
+ Remote: Compressing objects: 100% (14/14), done
68
+
69
+ Receiving objects (14)...
70
+ ___________________________ test_git_clone_with_path ___________________________
71
+
72
+ reset_directory = None
73
+ remote_repository = pygit2.Repository('/tmp/pytest-of-dag/pytest-14/local_remote_test_git_clone_with_path.git0/')
74
+ tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x7ff199ba1e50>, _basetemp=PosixPath('/tmp/pytest-of-dag/pytest-14'), _retention_count=3, _retention_policy='all')
75
+
76
+ def test_git_clone_with_path(reset_directory,
77
+ remote_repository,
78
+ tmp_path_factory):
79
+ path = tmp_path_factory.mktemp('clone-workdir')
80
+ os.chdir(path)
81
+
82
+ git = git_project.Git()
83
+
84
+ remote_url = 'file://' + remote_repository.path
85
+
86
+ path = Path.cwd() / 'foo' / 'bar'
87
+
88
+ os.makedirs(path)
89
+
90
+ path = path / 'test-clone'
91
+
92
+ > clone_path = git.clone(remote_url, path)
93
+
94
+ /home/dag/src/git-project/dev/tests/test_git.py:482:
95
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
96
+ /home/dag/src/git-project/dev/src/git_project/git.py:744: in clone
97
+ self._repo = pygit2.clone_repository(url, target_path, bare, callbacks=callbacks)
98
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/__init__.py:217: in clone_repository
99
+ payload.check_error(err)
100
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/callbacks.py:93: in check_error
101
+ raise self._stored_exception
102
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/callbacks.py:424: in wrapper
103
+ return f(*args)
104
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/callbacks.py:552: in _transfer_progress_cb
105
+ transfer_progress(TransferProgress(stats_ptr))
106
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
107
+
108
+ self = <git_project.git.Git.RemoteCallbacks object at 0x7ff198a8e350>
109
+ stats = <pygit2.remotes.TransferProgress object at 0x7ff198a8eb10>
110
+
111
+ def transfer_progress(self, stats):
112
+ if not self.started_transfer:
113
+ self.started_transfer = True
114
+ print(f'Receiving objects ({stats.total_objects})...')
115
+ > self.progress.start(max_value=stats.total_objects)
116
+ E TypeError: ProgressBar.start() got an unexpected keyword argument 'max_value'
117
+
118
+ /home/dag/src/git-project/dev/src/git_project/git.py:77: TypeError
119
+ ----------------------------- Captured stdout call -----------------------------
120
+ Remote: Counting objects 1
121
+ Remote: Counting objects 14
122
+ Remote: Counting objects 14
123
+
124
+ Remote: Compressing objects: 0% (0/14)
125
+ Remote: Compressing objects: 100% (14/14), done
126
+
127
+ Receiving objects (14)...
128
+ =========================== short test summary info ============================
129
+ FAILED tests/test_git.py::test_git_clone - TypeError: ProgressBar.start() got...
130
+ FAILED tests/test_git.py::test_git_clone_with_path - TypeError: ProgressBar.s...
131
+ ======================== 2 failed, 104 passed in 2.74s =========================
@@ -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,142 @@ 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}'
505
+
506
+ def test_substitutable_substitute_escaped_braces(reset_directory, git):
507
+ class MyProject(git_project.ScopedConfigObject):
508
+ def __init__(self):
509
+ super().__init__(git,
510
+ 'project',
511
+ None,
512
+ 'myproject',
513
+ builddir='{git_workdir}/{{}path{}}/to/build',
514
+ target='debug')
515
+
516
+ substitutable = MySubstitutable.get(git, 'project', 'test')
517
+
518
+ project = MyProject()
519
+
520
+ command = substitutable.substitute_value(git,
521
+ project,
522
+ substitutable.command)
523
+
524
+ assert command == f'cd {git.get_working_copy_root()}/{{path}}/to/build/{git.get_current_branch()} && make {project.target}'
525
+
526
+ def test_substitutable_substitute_preserve_escaped_braces(reset_directory, git):
527
+ class MyProject(git_project.ScopedConfigObject):
528
+ def __init__(self):
529
+ super().__init__(git,
530
+ 'project',
531
+ None,
532
+ 'myproject',
533
+ builddir='{git_workdir}/{{}{{}{}}path{{}{}}{}}/to/build',
534
+ target='debug')
535
+
536
+ substitutable = MySubstitutable.get(git, 'project', 'test')
537
+
538
+ project = MyProject()
539
+
540
+ command = substitutable.substitute_value(git,
541
+ project,
542
+ substitutable.command)
543
+
544
+ assert command == f'cd {git.get_working_copy_root()}/{{{{}}path{{}}}}/to/build/{git.get_current_branch()} && make {project.target}'
git_project-0.0.26/.git DELETED
@@ -1 +0,0 @@
1
- gitdir: /Users/dag/projects/git-project/.git/worktrees/master/
@@ -1,33 +0,0 @@
1
- #!/usr/bin/env python3
2
- #
3
- # Copyright 2020 David A. Greene
4
- #
5
- # This program is free software: you can redistribute it and/or modify it under
6
- # the terms of the GNU General Public License as published by the Free Software
7
- # Foundation, either version 3 of the License, or (at your option) any later
8
- # version.
9
- #
10
- # This program is distributed in the hope that it will be useful, but WITHOUT
11
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
- # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13
- # details.
14
- #
15
- # You should have received a copy of the GNU General Public License along with
16
- # this program. If not, see <https://www.gnu.org/licenses/>.
17
- #
18
-
19
- from .commandline import parse_arguments, add_top_level_command
20
- from .commandline import get_or_add_top_level_command
21
- from .configobj import ConfigObject
22
- from .exception import GitProjectException
23
- from .git import Git
24
- from .gitproject import GitProject
25
- from .main import main_impl
26
- from .parsermanager import ParserManager
27
- from .plugin import Plugin
28
- from .pluginmanager import PluginManager
29
- from .project import Project
30
- from .runnable import RunnableConfigObject
31
- from .scopedobj import ScopedConfigObject
32
- from .substitutable import SubstitutableConfigObject
33
- from .shell import run_command_with_shell, iter_command, capture_command