Skip to content

Commit f96ee74

Browse files
committed
index: added move method including test
test.helpers: temporary rw repository creators now set the working dir of the program, easing working with relative paths a lot
1 parent 76fd1d4 commit f96ee74

File tree

3 files changed

+132
-13
lines changed

3 files changed

+132
-13
lines changed

lib/git/index.py

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,20 @@ def add(self, items, force=True, fprogress=lambda *args: None):
986986

987987
return entries_added
988988

989+
def _items_to_rela_paths(self, items):
990+
"""Returns a list of repo-relative paths from the given items which
991+
may be absolute or relative paths, entries or blobs"""
992+
paths = list()
993+
for item in items:
994+
if isinstance(item, (BaseIndexEntry,Blob)):
995+
paths.append(self._to_relative_path(item.path))
996+
elif isinstance(item, basestring):
997+
paths.append(self._to_relative_path(item))
998+
else:
999+
raise TypeError("Invalid item type: %r" % item)
1000+
# END for each item
1001+
return paths
1002+
9891003
@clear_cache
9901004
@default_index
9911005
def remove(self, items, working_tree=False, **kwargs):
@@ -1021,7 +1035,8 @@ def remove(self, items, working_tree=False, **kwargs):
10211035
as 'r' to allow recurive removal of
10221036
10231037
Returns
1024-
List(path_string, ...) list of paths that have been removed effectively.
1038+
List(path_string, ...) list of repository relative paths that have
1039+
been removed effectively.
10251040
This is interesting to know in case you have provided a directory or
10261041
globs. Paths are relative to the repository.
10271042
"""
@@ -1031,22 +1046,84 @@ def remove(self, items, working_tree=False, **kwargs):
10311046
args.append("--")
10321047

10331048
# preprocess paths
1034-
paths = list()
1035-
for item in items:
1036-
if isinstance(item, (BaseIndexEntry,Blob)):
1037-
paths.append(self._to_relative_path(item.path))
1038-
elif isinstance(item, basestring):
1039-
paths.append(self._to_relative_path(item))
1040-
else:
1041-
raise TypeError("Invalid item type: %r" % item)
1042-
# END for each item
1043-
1049+
paths = self._items_to_rela_paths(items)
10441050
removed_paths = self.repo.git.rm(args, paths, **kwargs).splitlines()
10451051

10461052
# process output to gain proper paths
10471053
# rm 'path'
10481054
return [ p[4:-1] for p in removed_paths ]
1055+
1056+
@clear_cache
1057+
@default_index
1058+
def move(self, items, skip_errors=False, **kwargs):
1059+
"""
1060+
Rename/move the items, whereas the last item is considered the destination of
1061+
the move operation. If the destination is a file, the first item ( of two )
1062+
must be a file as well. If the destination is a directory, it may be preceeded
1063+
by one or more directories or files.
1064+
1065+
The working tree will be affected in non-bare repositories.
1066+
1067+
``items``
1068+
Multiple types of items are supported, please see the 'remove' method
1069+
for reference.
1070+
``skip_errors``
1071+
If True, errors such as ones resulting from missing source files will
1072+
be skpped.
1073+
``**kwargs``
1074+
Additional arguments you would like to pass to git-mv, such as dry_run
1075+
or force.
1076+
1077+
Returns
1078+
List(tuple(source_path_string, destination_path_string), ...)
1079+
A list of pairs, containing the source file moved as well as its
1080+
actual destination. Relative to the repository root.
1081+
1082+
Raises
1083+
ValueErorr: If only one item was given
1084+
GitCommandError: If git could not handle your request
1085+
"""
1086+
args = list()
1087+
if skip_errors:
1088+
args.append('-k')
1089+
1090+
paths = self._items_to_rela_paths(items)
1091+
if len(paths) < 2:
1092+
raise ValueError("Please provide at least one source and one destination of the move operation")
1093+
1094+
was_dry_run = kwargs.pop('dry_run', kwargs.pop('n', None))
1095+
kwargs['dry_run'] = True
1096+
1097+
# first execute rename in dryrun so the command tells us what it actually does
1098+
# ( for later output )
1099+
out = list()
1100+
mvlines = self.repo.git.mv(args, paths, **kwargs).splitlines()
1101+
1102+
# parse result - first 0:n/2 lines are 'checking ', the remaining ones
1103+
# are the 'renaming' ones which we parse
1104+
for ln in xrange(len(mvlines)/2, len(mvlines)):
1105+
tokens = mvlines[ln].split(' to ')
1106+
assert len(tokens) == 2, "Too many tokens in %s" % mvlines[ln]
1107+
1108+
# [0] = Renaming x
1109+
# [1] = y
1110+
out.append((tokens[0][9:], tokens[1]))
1111+
# END for each line to parse
1112+
1113+
# either prepare for the real run, or output the dry-run result
1114+
if was_dry_run:
1115+
return out
1116+
# END handle dryrun
10491117

1118+
1119+
# now apply the actual operation
1120+
kwargs.pop('dry_run')
1121+
self.repo.git.mv(args, paths, **kwargs)
1122+
1123+
return out
1124+
1125+
1126+
10501127
@default_index
10511128
def commit(self, message, parent_commits=None, head=True):
10521129
"""

test/git/test_index.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
import shutil
1515
from stat import *
1616

17-
class TestTree(TestBase):
17+
class TestIndex(TestBase):
1818

1919
def __init__(self, *args):
20-
super(TestTree, self).__init__(*args)
20+
super(TestIndex, self).__init__(*args)
2121
self._reset_progress()
2222

2323
def _assert_fprogress(self, entries):
@@ -498,3 +498,32 @@ def mixed_iterator():
498498
open(fake_symlink_path,'rb').read() == link_target
499499
else:
500500
assert S_ISLNK(os.lstat(fake_symlink_path)[ST_MODE])
501+
502+
# TEST RENAMING
503+
def assert_mv_rval(rval):
504+
for source, dest in rval:
505+
assert not os.path.exists(source) and os.path.exists(dest)
506+
# END for each renamed item
507+
# END move assertion utility
508+
509+
self.failUnlessRaises(ValueError, index.move, ['just_one_path'])
510+
# file onto existing file
511+
files = ['AUTHORS', 'LICENSE']
512+
self.failUnlessRaises(GitCommandError, index.move, files)
513+
514+
# again, with force
515+
assert_mv_rval(index.move(files, force=True))
516+
517+
# files into directory - dry run
518+
paths = ['LICENSE', 'VERSION', 'doc']
519+
rval = index.move(paths, dry_run=True)
520+
assert len(rval) == 2
521+
assert os.path.exists(paths[0])
522+
523+
# again, no dry run
524+
rval = index.move(paths)
525+
assert_mv_rval(rval)
526+
527+
# dir into dir
528+
rval = index.move(['doc', 'test'])
529+
assert_mv_rval(rval)

test/testlib/helper.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def case(self, rw_repo)
9090
def bare_repo_creator(self):
9191
repo_dir = tempfile.mktemp("bare_repo")
9292
rw_repo = self.rorepo.clone(repo_dir, shared=True, bare=True)
93+
prev_cwd = os.getcwd()
9394
try:
9495
return func(self, rw_repo)
9596
finally:
@@ -106,6 +107,9 @@ def with_rw_repo(working_tree_ref):
106107
out the working tree at the given working_tree_ref.
107108
108109
This repository type is more costly due to the working copy checkout.
110+
111+
To make working with relative paths easier, the cwd will be set to the working
112+
dir of the repository.
109113
"""
110114
assert isinstance(working_tree_ref, basestring), "Decorator requires ref name for working tree checkout"
111115
def argument_passer(func):
@@ -116,9 +120,12 @@ def repo_creator(self):
116120
rw_repo.head.commit = working_tree_ref
117121
rw_repo.head.reference.checkout()
118122

123+
prev_cwd = os.getcwd()
124+
os.chdir(rw_repo.working_dir)
119125
try:
120126
return func(self, rw_repo)
121127
finally:
128+
os.chdir(prev_cwd)
122129
rw_repo.git.clear_cache()
123130
shutil.rmtree(repo_dir, onerror=_rmtree_onerror)
124131
# END cleanup
@@ -148,6 +155,8 @@ def with_rw_and_rw_remote_repo(working_tree_ref):
148155
def case(self, rw_repo, rw_remote_repo)
149156
150157
This setup allows you to test push and pull scenarios and hooks nicely.
158+
159+
See working dir info in with_rw_repo
151160
"""
152161
assert isinstance(working_tree_ref, basestring), "Decorator requires ref name for working tree checkout"
153162
def argument_passer(func):
@@ -192,9 +201,13 @@ def remote_repo_creator(self):
192201
else:
193202
raise AssertionError('Please start a git-daemon to run this test, execute: git-daemon "%s"'%tempfile.gettempdir())
194203

204+
# adjust working dir
205+
prev_cwd = os.getcwd()
206+
os.chdir(rw_repo.working_dir)
195207
try:
196208
return func(self, rw_repo, rw_remote_repo)
197209
finally:
210+
os.chdir(prev_cwd)
198211
rw_repo.git.clear_cache()
199212
rw_remote_repo.git.clear_cache()
200213
shutil.rmtree(repo_dir, onerror=_rmtree_onerror)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy