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

Commit9fbae71

Browse files
committed
Greatly improved possible safety of Submodule.update(), which is used by default.
Previously, the implementation would gladly reset new commits in submodules,and/or reset a dirty working tree.Now the new force_reset/force flag has to be specified explicitly to get backto the old behaviour.All submodule tests except for one are working.
1 parentea29541 commit9fbae71

File tree

7 files changed

+170
-58
lines changed

7 files changed

+170
-58
lines changed

‎git/config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,10 +546,12 @@ def set_value(self, section, option, value):
546546
:param option: Name of the options whose value to set
547547
548548
:param value: Value to set the option to. It must be a string or convertible
549-
to a string"""
549+
to a string
550+
:return: this instance"""
550551
ifnotself.has_section(section):
551552
self.add_section(section)
552553
self.set(section,option,self._value_to_string(value))
554+
returnself
553555

554556
defrename_section(self,section,new_name):
555557
"""rename the given section to new_name

‎git/exc.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,14 @@ def __init__(self, command, status, stdout, stderr):
8484
def__str__(self):
8585
return ("'%s' hook returned with exit code %i\nstdout: '%s'\nstderr: '%s'"
8686
% (self.command,self.status,self.stdout,self.stderr))
87+
88+
89+
classRepositoryDirtyError(Exception):
90+
"""Thrown whenever an operation on a repository fails as it has uncommited changes that would be overwritten"""
91+
92+
def__init__(self,repo,message):
93+
self.repo=repo
94+
self.message=message
95+
96+
def__str__(self):
97+
return"Operation cannot be performed on %r: %s"% (self.repo,self.message)

‎git/objects/submodule/base.py

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
)
2525
fromgit.excimport (
2626
InvalidGitRepositoryError,
27-
NoSuchPathError
27+
NoSuchPathError,
28+
RepositoryDirtyError
2829
)
2930
fromgit.compatimport (
3031
string_types,
@@ -36,6 +37,7 @@
3637

3738
importos
3839
importlogging
40+
importtempfile
3941

4042
__all__= ["Submodule","UpdateProgress"]
4143

@@ -424,8 +426,8 @@ def add(cls, repo, name, path, url=None, branch=None, no_checkout=False):
424426

425427
returnsm
426428

427-
defupdate(self,recursive=False,init=True,to_latest_revision=False,progress=None,
428-
dry_run=False):
429+
defupdate(self,recursive=False,init=True,to_latest_revision=False,progress=None,dry_run=False,
430+
force=False):
429431
"""Update the repository of this submodule to point to the checkout
430432
we point at with the binsha of this instance.
431433
@@ -440,6 +442,12 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
440442
:param progress: UpdateProgress instance or None of no progress should be shown
441443
:param dry_run: if True, the operation will only be simulated, but not performed.
442444
All performed operations are read-only
445+
:param force:
446+
If True, we may reset heads even if the repository in question is dirty. Additinoally we will be allowed
447+
to set a tracking branch which is ahead of its remote branch back into the past or the location of the
448+
remote branch. This will essentially 'forget' commits.
449+
If False, local tracking branches that are in the future of their respective remote branches will simply
450+
not be moved.
443451
:note: does nothing in bare repositories
444452
:note: method is definitely not atomic if recurisve is True
445453
:return: self"""
@@ -565,23 +573,45 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
565573
# update the working tree
566574
# handles dry_run
567575
ifmrepoisnotNoneandmrepo.head.commit.binsha!=binsha:
576+
# We must assure that our destination sha (the one to point to) is in the future of our current head.
577+
# Otherwise, we will reset changes that might have been done on the submodule, but were not yet pushed
578+
# We also handle the case that history has been rewritten, leaving no merge-base. In that case
579+
# we behave conservatively, protecting possible changes the user had done
580+
may_reset=True
581+
ifmrepo.head.commit.binsha!=self.NULL_BIN_SHA:
582+
base_commit=mrepo.merge_base(mrepo.head.commit,hexsha)
583+
iflen(base_commit)==0orbase_commit[0].hexsha==hexsha:
584+
ifforce:
585+
log.debug("Will force checkout or reset on local branch that is possibly in the future of"+
586+
"the commit it will be checked out to, effectively 'forgetting' new commits")
587+
else:
588+
log.info("Skipping %s on branch '%s' of submodule repo '%s' as it contains un-pushed commits",
589+
is_detachedand"checkout"or"reset",mrepo.head,mrepo)
590+
may_reset=False
591+
# end handle force
592+
# end handle if we are in the future
593+
594+
ifmay_resetandnotforceandmrepo.is_dirty(index=True,working_tree=True,untracked_files=True):
595+
raiseRepositoryDirtyError(mrepo,"Cannot reset a dirty repository")
596+
# end handle force and dirty state
597+
# end handle empty repo
598+
599+
# end verify future/past
568600
progress.update(BEGIN|UPDWKTREE,0,1,prefix+
569601
"Updating working tree at %s for submodule %r to revision %s"
570602
% (self.path,self.name,hexsha))
571-
ifnotdry_run:
603+
604+
ifnotdry_runandmay_reset:
572605
ifis_detached:
573606
# NOTE: for now we force, the user is no supposed to change detached
574607
# submodules anyway. Maybe at some point this becomes an option, to
575608
# properly handle user modifications - see below for future options
576609
# regarding rebase and merge.
577-
mrepo.git.checkout(hexsha,force=True)
610+
mrepo.git.checkout(hexsha,force=force)
578611
else:
579-
# TODO: allow to specify a rebase, merge, or reset
580-
# TODO: Warn if the hexsha forces the tracking branch off the remote
581-
# branch - this should be prevented when setting the branch option
582612
mrepo.head.reset(hexsha,index=True,working_tree=True)
583613
# END handle checkout
584-
#END handle dry_run
614+
#if we may reset/checkout
585615
progress.update(END|UPDWKTREE,0,1,prefix+"Done updating working tree for submodule %r"%self.name)
586616
# END update to new commit only if needed
587617

@@ -591,7 +621,8 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
591621
# in dry_run mode, the module might not exist
592622
ifmrepoisnotNone:
593623
forsubmoduleinself.iter_items(self.module()):
594-
submodule.update(recursive,init,to_latest_revision,progress=progress,dry_run=dry_run)
624+
submodule.update(recursive,init,to_latest_revision,progress=progress,dry_run=dry_run,
625+
force=force)
595626
# END handle recursive update
596627
# END handle dry run
597628
# END for each submodule
@@ -748,7 +779,7 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
748779
csm.remove(module,force,configuration,dry_run)
749780
del(csm)
750781
# end
751-
ifnc>0:
782+
ifnotdry_runandnc>0:
752783
# Assure we don't leave the parent repository in a dirty state, and commit our changes
753784
# It's important for recursive, unforced, deletions to work as expected
754785
self.module().index.commit("Removed submodule '%s'"%self.name)
@@ -854,8 +885,7 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
854885
defset_parent_commit(self,commit,check=True):
855886
"""Set this instance to use the given commit whose tree is supposed to
856887
contain the .gitmodules blob.
857-
858-
:param commit: Commit'ish reference pointing at the root_tree
888+
:param commit: Commit'ish reference pointing at the root_tree, or None always point to the most recent commit
859889
:param check: if True, relatively expensive checks will be performed to verify
860890
validity of the submodule.
861891
:raise ValueError: if the commit's tree didn't contain the .gitmodules blob.
@@ -939,7 +969,7 @@ def rename(self, new_name):
939969
pw.release()
940970

941971
# .gitmodules
942-
cw=self.config_writer().config
972+
cw=self.config_writer(write=False).config
943973
cw.rename_section(sm_section(self.name),sm_section(new_name))
944974
cw.release()
945975

@@ -948,9 +978,16 @@ def rename(self, new_name):
948978
# .git/modules
949979
mod=self.module()
950980
ifmod.has_separate_working_tree():
951-
module_abspath=self._module_abspath(self.repo,self.path,new_name)
952-
os.renames(mod.git_dir,module_abspath)
953-
self._write_git_file_and_module_config(mod.working_tree_dir,module_abspath)
981+
destination_module_abspath=self._module_abspath(self.repo,self.path,new_name)
982+
source_dir=mod.git_dir
983+
# Let's be sure the submodule name is not so obviously tied to a directory
984+
ifdestination_module_abspath.startswith(mod.git_dir):
985+
tmp_dir=self._module_abspath(self.repo,self.path,os.path.basename(tempfile.mkdtemp()))
986+
os.renames(source_dir,tmp_dir)
987+
source_dir=tmp_dir
988+
# end handle self-containment
989+
os.renames(source_dir,destination_module_abspath)
990+
self._write_git_file_and_module_config(mod.working_tree_dir,destination_module_abspath)
954991
# end move separate git repository
955992

956993
returnself

‎git/objects/submodule/root.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def _clear_cache(self):
6161
#{ Interface
6262

6363
defupdate(self,previous_commit=None,recursive=True,force_remove=False,init=True,
64-
to_latest_revision=False,progress=None,dry_run=False):
64+
to_latest_revision=False,progress=None,dry_run=False,force_reset=False):
6565
"""Update the submodules of this repository to the current HEAD commit.
6666
This method behaves smartly by determining changes of the path of a submodules
6767
repository, next to changes to the to-be-checked-out commit or the branch to be
@@ -80,10 +80,16 @@ def update(self, previous_commit=None, recursive=True, force_remove=False, init=
8080
:param init: If we encounter a new module which would need to be initialized, then do it.
8181
:param to_latest_revision: If True, instead of checking out the revision pointed to
8282
by this submodule's sha, the checked out tracking branch will be merged with the
83-
newest remote branch fetched from the repository's origin
83+
latest remote branch fetched from the repository's origin.
84+
Unless force_reset is specified, a local tracking branch will never be reset into its past, therefore
85+
the remote branch must be in the future for this to have an effect.
86+
:param force_reset: if True, submodules may checkout or reset their branch even if the repository has
87+
pending changes that would be overwritten, or if the local tracking branch is in the future of the
88+
remote tracking branch and would be reset into its past.
8489
:param progress: RootUpdateProgress instance or None if no progress should be sent
8590
:param dry_run: if True, operations will not actually be performed. Progress messages
86-
will change accordingly to indicate the WOULD DO state of the operation."""
91+
will change accordingly to indicate the WOULD DO state of the operation.
92+
:return: self"""
8793
ifself.repo.bare:
8894
raiseInvalidGitRepositoryError("Cannot update submodules in bare repositories")
8995
# END handle bare
@@ -134,9 +140,7 @@ def update(self, previous_commit=None, recursive=True, force_remove=False, init=
134140
# of previous module. Trigger the cache to be updated before that
135141
progress.update(op,i,len_rrsm,prefix+"Removing submodule %r at %s"% (rsm.name,rsm.abspath))
136142
rsm._parent_commit=repo.head.commit
137-
ifnotdry_run:
138-
rsm.remove(configuration=False,module=True,force=force_remove)
139-
# END handle dry-run
143+
rsm.remove(configuration=False,module=True,force=force_remove,dry_run=dry_run)
140144

141145
ifi==len_rrsm-1:
142146
op|=END
@@ -311,7 +315,7 @@ def update(self, previous_commit=None, recursive=True, force_remove=False, init=
311315
forsminsms:
312316
# update the submodule using the default method
313317
sm.update(recursive=False,init=init,to_latest_revision=to_latest_revision,
314-
progress=progress,dry_run=dry_run)
318+
progress=progress,dry_run=dry_run,force=force_reset)
315319

316320
# update recursively depth first - question is which inconsitent
317321
# state will be better in case it fails somewhere. Defective branch
@@ -322,11 +326,13 @@ def update(self, previous_commit=None, recursive=True, force_remove=False, init=
322326
ifsm.module_exists():
323327
type(self)(sm.module()).update(recursive=True,force_remove=force_remove,
324328
init=init,to_latest_revision=to_latest_revision,
325-
progress=progress,dry_run=dry_run)
329+
progress=progress,dry_run=dry_run,force_reset=force_reset)
326330
# END handle dry_run
327331
# END handle recursive
328332
# END for each submodule to update
329333

334+
returnself
335+
330336
defmodule(self):
331337
""":return: the actual repository containing the submodules"""
332338
returnself.repo

‎git/repo/fun.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ def rev_parse(repo, rev):
296296
raiseValueError("Invalid token: %r"%token)
297297
# END end handle tag
298298
except (IndexError,AttributeError):
299-
raiseBadObject("InvalidRevision in %s"%rev)
299+
raiseBadName("Invalidrevision spec '%s' - not enough parent commits to reach '%s%i'"%(rev,token,num))
300300
# END exception handling
301301
# END parse loop
302302

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp