Skip to content

Commit 67680a0

Browse files
committed
Assured Submodule.(update|move) are dealing with .git files appropriately.
However, a simple test-case still fails for reasons not yet understood. There is more to be fixed here - .remove() still fails.
1 parent 503b62b commit 67680a0

File tree

2 files changed

+106
-60
lines changed

2 files changed

+106
-60
lines changed

git/objects/submodule/base.py

+92-60
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525
InvalidGitRepositoryError,
2626
NoSuchPathError
2727
)
28-
from git.compat import string_types
28+
from git.compat import (
29+
string_types,
30+
defenc
31+
)
2932

3033
import stat
3134
import git
@@ -209,13 +212,64 @@ def _module_abspath(cls, parent_repo, path, name):
209212
# end
210213

211214
@classmethod
212-
def _write_git_file(cls, working_tree_dir, module_abspath, overwrite_existing=False):
215+
def _clone_repo(cls, repo, url, path, name, **kwargs):
216+
""":return: Repo instance of newly cloned repository
217+
:param repo: our parent repository
218+
:param url: url to clone from
219+
:param path: repository-relative path to the submodule checkout location
220+
:param name: canonical of the submodule
221+
:param kwrags: additinoal arguments given to git.clone"""
222+
module_abspath = cls._module_abspath(repo, path, name)
223+
module_checkout_path = module_abspath
224+
if cls._need_gitfile_submodules(repo.git):
225+
kwargs['separate_git_dir'] = module_abspath
226+
module_abspath_dir = os.path.dirname(module_abspath)
227+
if not os.path.isdir(module_abspath_dir):
228+
os.makedirs(module_abspath_dir)
229+
module_checkout_path = os.path.join(repo.working_tree_dir, path)
230+
# end
231+
232+
clone = git.Repo.clone_from(url, module_checkout_path, **kwargs)
233+
if cls._need_gitfile_submodules(repo.git):
234+
cls._write_git_file(module_checkout_path, module_abspath)
235+
# end
236+
return clone
237+
238+
@classmethod
239+
def _to_relative_path(cls, parent_repo, path):
240+
""":return: a path guaranteed to be relative to the given parent-repository
241+
:raise ValueError: if path is not contained in the parent repository's working tree"""
242+
path = to_native_path_linux(path)
243+
if path.endswith('/'):
244+
path = path[:-1]
245+
# END handle trailing slash
246+
247+
if os.path.isabs(path):
248+
working_tree_linux = to_native_path_linux(parent_repo.working_tree_dir)
249+
if not path.startswith(working_tree_linux):
250+
raise ValueError("Submodule checkout path '%s' needs to be within the parents repository at '%s'"
251+
% (working_tree_linux, path))
252+
path = path[len(working_tree_linux) + 1:]
253+
if not path:
254+
raise ValueError("Absolute submodule path '%s' didn't yield a valid relative path" % path)
255+
# end verify converted relative path makes sense
256+
# end convert to a relative path
257+
258+
return path
259+
260+
@classmethod
261+
def _write_git_file(cls, working_tree_dir, module_abspath):
213262
"""Writes a .git file containing a (preferably) relative path to the actual git module repository.
214263
It is an error if the module_abspath cannot be made into a relative path, relative to the working_tree_dir
264+
:note: will overwrite existing files !
215265
:param working_tree_dir: directory to write the .git file into
216266
:param module_abspath: absolute path to the bare repository
217-
:param overwrite_existing: if True, we may rewrite existing .git files, otherwise we raise"""
218-
raise NotImplementedError
267+
"""
268+
git_file = os.path.join(working_tree_dir, '.git')
269+
rela_path = os.path.relpath(module_abspath, start=working_tree_dir)
270+
fp = open(git_file, 'wb')
271+
fp.write(("gitdir: %s" % rela_path).encode(defenc))
272+
fp.close()
219273

220274
#{ Edit Interface
221275

@@ -252,21 +306,7 @@ def add(cls, repo, name, path, url=None, branch=None, no_checkout=False):
252306
raise InvalidGitRepositoryError("Cannot add submodules to bare repositories")
253307
# END handle bare repos
254308

255-
path = to_native_path_linux(path)
256-
if path.endswith('/'):
257-
path = path[:-1]
258-
# END handle trailing slash
259-
260-
if os.path.isabs(path):
261-
working_tree_linux = to_native_path_linux(repo.working_tree_dir)
262-
if not path.startswith(working_tree_linux):
263-
raise ValueError("Submodule checkout path '%s' needs to be within the parents repository at '%s'"
264-
% (working_tree_linux, path))
265-
path = path[len(working_tree_linux) + 1:]
266-
if not path:
267-
raise ValueError("Absolute submodule path '%s' didn't yield a valid relative path" % path)
268-
# end verify converted relative path makes sense
269-
# end convert to a relative path
309+
path = cls._to_relative_path(repo, path)
270310

271311
# assure we never put backslashes into the url, as some operating systems
272312
# like it ...
@@ -317,17 +357,9 @@ def add(cls, repo, name, path, url=None, branch=None, no_checkout=False):
317357
if not branch_is_default:
318358
kwargs['b'] = br.name
319359
# END setup checkout-branch
320-
module_abspath = cls._module_abspath(repo, path, name)
321-
module_checkout_path = module_abspath
322-
if cls._need_gitfile_submodules(repo.git):
323-
kwargs['separate_git_dir'] = module_abspath
324-
module_abspath_dir = os.path.dirname(module_abspath)
325-
if not os.path.isdir(module_abspath_dir):
326-
os.makedirs(module_abspath_dir)
327-
module_checkout_path = os.path.join(repo.working_tree_dir, path)
328-
# end
329-
330-
mrepo = git.Repo.clone_from(url, module_checkout_path, **kwargs)
360+
361+
# _clone_repo(cls, repo, url, path, name, **kwargs):
362+
mrepo = cls._clone_repo(repo, url, path, name, **kwargs)
331363
# END verify url
332364

333365
# update configuration and index
@@ -416,7 +448,6 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
416448
if not init:
417449
return self
418450
# END early abort if init is not allowed
419-
import git
420451

421452
# there is no git-repository yet - but delete empty paths
422453
module_path = self._module_abspath(self.repo, self.path, self.name)
@@ -433,7 +464,7 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
433464
progress.update(BEGIN | CLONE, 0, 1, prefix + "Cloning %s to %s in submodule %r" %
434465
(self.url, module_path, self.name))
435466
if not dry_run:
436-
mrepo = git.Repo.clone_from(self.url, module_path, n=True)
467+
mrepo = self._clone_repo(self.repo, self.url, self.path, self.name, n=True)
437468
# END handle dry-run
438469
progress.update(END | CLONE, 0, 1, prefix + "Done cloning to %s" % module_path)
439470

@@ -533,16 +564,15 @@ def move(self, module_path, configuration=True, module=True):
533564
the repository at our current path, changing the configuration, as well as
534565
adjusting our index entry accordingly.
535566
536-
:param module_path: the path to which to move our module, given as
537-
repository-relative path. Intermediate directories will be created
567+
:param module_path: the path to which to move our module in the parent repostory's working tree,
568+
given as repository-relative or absolute path. Intermediate directories will be created
538569
accordingly. If the path already exists, it must be empty.
539-
Trailling (back)slashes are removed automatically
570+
Trailing (back)slashes are removed automatically
540571
:param configuration: if True, the configuration will be adjusted to let
541572
the submodule point to the given path.
542573
:param module: if True, the repository managed by this submodule
543-
will be moved, not the configuration. This will effectively
544-
leave your repository in an inconsistent state unless the configuration
545-
and index already point to the target location.
574+
will be moved as well. If False, we don't move the submodule's checkout, which may leave
575+
the parent repository in an inconsistent state.
546576
:return: self
547577
:raise ValueError: if the module path existed and was not empty, or was a file
548578
:note: Currently the method is not atomic, and it could leave the repository
@@ -552,53 +582,55 @@ def move(self, module_path, configuration=True, module=True):
552582
raise ValueError("You must specify to move at least the module or the configuration of the submodule")
553583
# END handle input
554584

555-
module_path = to_native_path_linux(module_path)
556-
if module_path.endswith('/'):
557-
module_path = module_path[:-1]
558-
# END handle trailing slash
585+
module_checkout_path = self._to_relative_path(self.repo, module_path)
559586

560587
# VERIFY DESTINATION
561-
if module_path == self.path:
588+
if module_checkout_path == self.path:
562589
return self
563590
# END handle no change
564591

565-
dest_path = join_path_native(self.repo.working_tree_dir, module_path)
566-
if os.path.isfile(dest_path):
567-
raise ValueError("Cannot move repository onto a file: %s" % dest_path)
592+
module_checkout_abspath = join_path_native(self.repo.working_tree_dir, module_checkout_path)
593+
if os.path.isfile(module_checkout_abspath):
594+
raise ValueError("Cannot move repository onto a file: %s" % module_checkout_abspath)
568595
# END handle target files
569596

570597
index = self.repo.index
571-
tekey = index.entry_key(module_path, 0)
598+
tekey = index.entry_key(module_checkout_path, 0)
572599
# if the target item already exists, fail
573600
if configuration and tekey in index.entries:
574-
raise ValueError("Index entry for target path did alredy exist")
601+
raise ValueError("Index entry for target path did already exist")
575602
# END handle index key already there
576603

577604
# remove existing destination
578605
if module:
579-
if os.path.exists(dest_path):
580-
if len(os.listdir(dest_path)):
606+
if os.path.exists(module_checkout_abspath):
607+
if len(os.listdir(module_checkout_abspath)):
581608
raise ValueError("Destination module directory was not empty")
582-
# END handle non-emptyness
609+
# END handle non-emptiness
583610

584-
if os.path.islink(dest_path):
585-
os.remove(dest_path)
611+
if os.path.islink(module_checkout_abspath):
612+
os.remove(module_checkout_abspath)
586613
else:
587-
os.rmdir(dest_path)
614+
os.rmdir(module_checkout_abspath)
588615
# END handle link
589616
else:
590617
# recreate parent directories
591618
# NOTE: renames() does that now
592619
pass
593-
# END handle existance
620+
# END handle existence
594621
# END handle module
595622

596623
# move the module into place if possible
597624
cur_path = self.abspath
598625
renamed_module = False
599626
if module and os.path.exists(cur_path):
600-
os.renames(cur_path, dest_path)
627+
os.renames(cur_path, module_checkout_abspath)
601628
renamed_module = True
629+
630+
if self._need_gitfile_submodules(self.repo.git):
631+
module_abspath = self._module_abspath(self.repo, self.path, self.name)
632+
self._write_git_file(module_checkout_abspath, module_abspath)
633+
# end handle git file rewrite
602634
# END move physical module
603635

604636
# rename the index entry - have to manipulate the index directly as
@@ -609,22 +641,22 @@ def move(self, module_path, configuration=True, module=True):
609641
ekey = index.entry_key(self.path, 0)
610642
entry = index.entries[ekey]
611643
del(index.entries[ekey])
612-
nentry = git.IndexEntry(entry[:3] + (module_path,) + entry[4:])
644+
nentry = git.IndexEntry(entry[:3] + (module_checkout_path,) + entry[4:])
613645
index.entries[tekey] = nentry
614646
except KeyError:
615647
raise InvalidGitRepositoryError("Submodule's entry at %r did not exist" % (self.path))
616648
# END handle submodule doesn't exist
617649

618650
# update configuration
619651
writer = self.config_writer(index=index) # auto-write
620-
writer.set_value('path', module_path)
621-
self.path = module_path
652+
writer.set_value('path', module_checkout_path)
653+
self.path = module_checkout_path
622654
writer.release()
623655
del(writer)
624656
# END handle configuration flag
625657
except Exception:
626658
if renamed_module:
627-
os.renames(dest_path, cur_path)
659+
os.renames(module_checkout_abspath, cur_path)
628660
# END undo module renaming
629661
raise
630662
# END handle undo rename

git/test/test_submodule.py

+14
Original file line numberDiff line numberDiff line change
@@ -656,3 +656,17 @@ def test_git_submodule_compatibility(self, rwdir):
656656
assert os.path.isfile(module_repo_path)
657657
assert find_git_dir(module_repo_path) is not None, "module pointed to by .git file must be valid"
658658
# end verify submodule 'style'
659+
660+
# test move
661+
new_sm_path = 'submodules/one'
662+
sm.set_parent_commit(parent.commit())
663+
sm.move(new_sm_path)
664+
assert sm.exists()
665+
assert sm.module_exists()
666+
667+
# remove
668+
sm.remove()
669+
assert sm.exist()
670+
sm_module_path = sm.module().git_dir
671+
assert sm.module_exists()
672+
assert os.path.isdir(sm_module_path)

0 commit comments

Comments
 (0)