Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit3d061a1

Browse files
committed
Implemented deletion of submodules including proper tests
1 parent78d2cd6 commit3d061a1

File tree

2 files changed

+173
-3
lines changed

2 files changed

+173
-3
lines changed

‎lib/git/objects/submodule.py

Lines changed: 123 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@
1010
importos
1111
importsys
1212
importweakref
13+
importshutil
1314

1415
__all__= ("Submodule","RootModule")
1516

1617
#{ Utilities
1718

18-
defsm_section(path):
19+
defsm_section(name):
1920
""":return: section title used in .gitmodules configuration file"""
20-
return'submodule "%s"'%path
21+
return'submodule "%s"'%name
2122

2223
defsm_name(section):
2324
""":return: name of the submodule as parsed from the section name"""
@@ -223,6 +224,7 @@ def update(self, recursive=False, init=True, to_latest_revision=False):
223224
if the remote repository had a master branch, or of the 'branch' option
224225
was specified for this submodule and the branch existed remotely
225226
:note: does nothing in bare repositories
227+
:note: method is definitely not atomic if recurisve is True
226228
:return: self"""
227229
ifself.repo.bare:
228230
returnself
@@ -329,6 +331,111 @@ def update(self, recursive=False, init=True, to_latest_revision=False):
329331

330332
returnself
331333

334+
defremove(self,module=True,force=False,configuration=True,dry_run=False):
335+
"""Remove this submodule from the repository. This will remove our entry
336+
from the .gitmodules file and the entry in the .git/config file.
337+
:param module: If True, the module we point to will be deleted
338+
as well. If the module is currently on a commit which is not part
339+
of any branch in the remote, if the currently checked out branch
340+
is ahead of its tracking branch, if you have modifications in the
341+
working tree, or untracked files,
342+
In case the removal of the repository fails for these reasons, the
343+
submodule status will not have been altered.
344+
If this submodule has child-modules on its own, these will be deleted
345+
prior to touching the own module.
346+
:param force: Enforces the deletion of the module even though it contains
347+
modifications. This basically enforces a brute-force file system based
348+
deletion.
349+
:param configuration: if True, the submodule is deleted from the configuration,
350+
otherwise it isn't. Although this should be enabled most of the times,
351+
this flag enables you to safely delete the repository of your submodule.
352+
:param dry_run: if True, we will not actually do anything, but throw the errors
353+
we would usually throw
354+
:note: doesn't work in bare repositories
355+
:raise InvalidGitRepositoryError: thrown if the repository cannot be deleted
356+
:raise OSError: if directories or files could not be removed"""
357+
ifself.repo.bare:
358+
raiseInvalidGitRepositoryError("Cannot delete a submodule in bare repository")
359+
# END handle bare mode
360+
361+
ifnot (module+configuration):
362+
raiseValueError("Need to specify to delete at least the module, or the configuration")
363+
# END handle params
364+
365+
# DELETE MODULE REPOSITORY
366+
##########################
367+
ifmoduleandself.module_exists():
368+
ifforce:
369+
# take the fast lane and just delete everything in our module path
370+
# TODO: If we run into permission problems, we have a highly inconsistent
371+
# state. Delete the .git folders last, start with the submodules first
372+
mp=self.module_path()
373+
method=None
374+
ifos.path.islink(mp):
375+
method=os.remove
376+
elifos.path.isdir(mp):
377+
method=shutil.rmtree
378+
elifos.path.exists(mp):
379+
raiseAssertionError("Cannot forcibly delete repository as it was neither a link, nor a directory")
380+
#END handle brutal deletion
381+
ifnotdry_run:
382+
assertmethod
383+
method(mp)
384+
#END apply deletion method
385+
else:
386+
# verify we may delete our module
387+
mod=self.module()
388+
ifmod.is_dirty(untracked_files=True):
389+
raiseInvalidGitRepositoryError("Cannot delete module at %s with any modifications, unless force is specified"%mod.working_tree_dir)
390+
# END check for dirt
391+
392+
# figure out whether we have new commits compared to the remotes
393+
# NOTE: If the user pulled all the time, the remote heads might
394+
# not have been updated, so commits coming from the remote look
395+
# as if they come from us. But we stay strictly read-only and
396+
# don't fetch beforhand.
397+
forremoteinmod.remotes:
398+
num_branches_with_new_commits=0
399+
rrefs=remote.refs
400+
forrrefinrrefs:
401+
num_branches_with_new_commits=len(mod.git.cherry(rref))!=0
402+
# END for each remote ref
403+
# not a single remote branch contained all our commits
404+
ifnum_branches_with_new_commits==len(rrefs):
405+
raiseInvalidGitRepositoryError("Cannot delete module at %s as there are new commits"%mod.working_tree_dir)
406+
# END handle new commits
407+
# END for each remote
408+
409+
# gently remove all submodule repositories
410+
forsminself.children():
411+
sm.remove(module=True,force=False,configuration=False,dry_run=dry_run)
412+
# END for each child-submodule
413+
414+
# finally delete our own submodule
415+
ifnotdry_run:
416+
shutil.rmtree(mod.working_tree_dir)
417+
# END delete tree if possible
418+
# END handle force
419+
# END handle module deletion
420+
421+
# DELETE CONFIGURATION
422+
######################
423+
ifconfigurationandnotdry_run:
424+
# first the index-entry
425+
index=self.repo.index
426+
try:
427+
del(index.entries[index.entry_key(self.path,0)])
428+
exceptKeyError:
429+
pass
430+
#END delete entry
431+
index.write()
432+
433+
# now git config - need the config intact, otherwise we can't query
434+
# inforamtion anymore
435+
self.repo.config_writer().remove_section(sm_section(self.name))
436+
self.config_writer().remove_section()
437+
# END delete configuration
438+
332439
defset_parent_commit(self,commit,check=True):
333440
"""Set this instance to use the given commit whose tree is supposed to
334441
contain the .gitmodules blob.
@@ -410,10 +517,23 @@ def module_exists(self):
410517
try:
411518
self.module()
412519
returnTrue
413-
exceptInvalidGitRepositoryError:
520+
exceptException:
414521
returnFalse
415522
# END handle exception
416523

524+
defexists(self):
525+
""":return: True if the submodule exists, False otherwise. Please note that
526+
a submodule may exist (in the .gitmodules file) even though its module
527+
doesn't exist"""
528+
self._clear_cache()
529+
try:
530+
self.path
531+
returnTrue
532+
exceptException:
533+
# we raise if the path cannot be restored from configuration
534+
returnFalse
535+
# END handle exceptions
536+
417537
@property
418538
defbranch(self):
419539
""":return: The branch name that we are to checkout"""

‎test/git/test_submodule.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def _do_base_tests(self, rwrepo):
8787
# module retrieval is not always possible
8888
ifrwrepo.bare:
8989
self.failUnlessRaises(InvalidGitRepositoryError,sm.module)
90+
self.failUnlessRaises(InvalidGitRepositoryError,sm.remove)
9091
else:
9192
# its not checked out in our case
9293
self.failUnlessRaises(InvalidGitRepositoryError,sm.module)
@@ -155,6 +156,55 @@ def _do_base_tests(self, rwrepo):
155156
# undo the changes
156157
sm.module().head.ref=smref
157158
csm.module().head.ref.set_tracking_branch(csm_tracking_branch)
159+
160+
# REMOVAL OF REPOSITOTRY
161+
########################
162+
# must delete something
163+
self.failUnlessRaises(ValueError,csm.remove,module=False,configuration=False)
164+
# We have modified the configuration, hence the index is dirty, and the
165+
# deletion will fail
166+
# NOTE: As we did a few updates in the meanwhile, the indices where reset
167+
# Hence we restore some changes
168+
sm.config_writer().set_value("somekey","somevalue")
169+
csm.config_writer().set_value("okey","ovalue")
170+
self.failUnlessRaises(InvalidGitRepositoryError,sm.remove)
171+
# if we remove the dirty index, it would work
172+
sm.module().index.reset()
173+
# still, we have the file modified
174+
self.failUnlessRaises(InvalidGitRepositoryError,sm.remove,dry_run=True)
175+
sm.module().index.reset(working_tree=True)
176+
177+
# this would work
178+
sm.remove(dry_run=True)
179+
assertsm.module_exists()
180+
sm.remove(force=True,dry_run=True)
181+
assertsm.module_exists()
182+
183+
# but ... we have untracked files in the child submodule
184+
fn=join_path_native(csm.module().working_tree_dir,"newfile")
185+
open(fn,'w').write("hi")
186+
self.failUnlessRaises(InvalidGitRepositoryError,sm.remove)
187+
188+
# forcibly delete the child repository
189+
csm.remove(force=True)
190+
assertnotcsm.exists()
191+
assertnotcsm.module_exists()
192+
assertlen(sm.children())==0
193+
# now we have a changed index, as configuration was altered.
194+
# fix this
195+
sm.module().index.reset(working_tree=True)
196+
197+
# now delete only the module of the main submodule
198+
assertsm.module_exists()
199+
sm.remove(configuration=False)
200+
assertsm.exists()
201+
assertnotsm.module_exists()
202+
assertsm.config_reader().get_value('url')
203+
204+
# delete the rest
205+
sm.remove()
206+
assertnotsm.exists()
207+
assertnotsm.module_exists()
158208
# END handle bare mode
159209

160210

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp