diff --git a/Documentation/git-filter-repo.txt b/Documentation/git-filter-repo.txt index a19edf8c..e4c99e43 100644 --- a/Documentation/git-filter-repo.txt +++ b/Documentation/git-filter-repo.txt @@ -398,10 +398,10 @@ Reference map ~~~~~~~~~~~~~ The `$GIT_DIR/filter-repo/ref-map` file contains a mapping of which local -references were changed. +references were (or were not) changed. * A header is the first line with the text "old", "new" and "ref" - * Reference mappings are in no particular order + * Reference mappings are sorted by ref * An all-zeros hash, or null SHA, represents a non-existent object. When in the "new" column, this means the ref was removed entirely. diff --git a/git-filter-repo b/git-filter-repo index 3b305b7e..ee6da900 100755 --- a/git-filter-repo +++ b/git-filter-repo @@ -2947,6 +2947,9 @@ class RepoFilter(object): # now-missing commit hash, since there was nothing to map it to. self._commits_referenced_but_removed = set() + # Other vars related to metadata tracking + self._already_ran = False + # Progress handling (number of commits parsed, etc.) self._progress_writer = ProgressWriter() self._num_commits = 0 @@ -2967,18 +2970,17 @@ class RepoFilter(object): # Compile some regexes and cache those self._hash_re = re.compile(br'(\b[0-9a-f]{7,40}\b)') - self._full_hash_re = re.compile(br'(\b[0-9a-f]{40}\b)') def _handle_arg_callbacks(self): - def make_callback(argname, str): + def make_callback(argname, bdy): callback_globals = {g: globals()[g] for g in public_globals} callback_locals = {} exec('def callback({}, _do_not_use_this_var = None):\n'.format(argname)+ - ' '+'\n '.join(str.splitlines()), callback_globals, callback_locals) + ' '+'\n '.join(bdy.splitlines()), callback_globals, callback_locals) return callback_locals['callback'] - def handle(type): - callback_field = '_{}_callback'.format(type) - code_string = getattr(self._args, type+'_callback') + def handle(which): + callback_field = '_{}_callback'.format(which) + code_string = getattr(self._args, which+'_callback') if code_string: if os.path.exists(code_string): with open(code_string, 'r', encoding='utf-8') as f: @@ -2986,12 +2988,12 @@ class RepoFilter(object): if getattr(self, callback_field): raise SystemExit(_("Error: Cannot pass a %s_callback to RepoFilter " "AND pass --%s-callback" - % (type, type))) + % (which, which))) if 'return ' not in code_string and \ - type not in ('blob', 'commit', 'tag', 'reset'): + which not in ('blob', 'commit', 'tag', 'reset'): raise SystemExit(_("Error: --%s-callback should have a return statement") - % type) - setattr(self, callback_field, make_callback(type, code_string)) + % which) + setattr(self, callback_field, make_callback(which, code_string)) handle('filename') handle('message') handle('name') @@ -3023,8 +3025,8 @@ class RepoFilter(object): # Determine if this is second or later run of filter-repo tmp_dir = self.results_tmp_dir(create_if_missing=False) ran_path = os.path.join(tmp_dir, b'already_ran') - already_ran = os.path.isfile(ran_path) - if already_ran: + self._already_ran = os.path.isfile(ran_path) + if self._already_ran: current_time = time.time() file_mod_time = os.path.getmtime(ran_path) file_age = current_time - file_mod_time @@ -3036,17 +3038,17 @@ class RepoFilter(object): if response.lower() != 'y': os.remove(ran_path) - already_ran = False + self._already_ran = False # Default for --replace-refs if not self._args.replace_refs: self._args.replace_refs = 'delete-no-add' if self._args.replace_refs == 'old-default': - self._args.replace_refs = ('update-or-add' if already_ran + self._args.replace_refs = ('update-or-add' if self._already_ran else 'update-and-add') # Do sanity checks from the correct directory - if not self._args.force and not already_ran: + if not self._args.force and not self._already_ran: cwd = os.getcwd() os.chdir(target_working_dir) RepoFilter.sanity_check(self._orig_refs, is_bare, self._config_settings) @@ -3278,13 +3280,6 @@ class RepoFilter(object): assert new_hash is not None return new_hash[0:orig_len] - def _translate_full_commit_hash(self, matchobj): - old_hash = matchobj.group(1) - new_hash = self._get_rename(old_hash) - if new_hash is None: - return old_hash - return new_hash - def _maybe_trim_extra_parents(self, orig_parents, parents): '''Due to pruning of empty commits, some parents could be non-existent (None) or otherwise redundant. Remove the non-existent parents, and @@ -3754,6 +3749,10 @@ class RepoFilter(object): orig_file_changes = set(commit.file_changes) self._filter_files(commit) + # Call the user-defined callback, if any + if self._commit_callback: + self._commit_callback(commit, self.callback_metadata(aux_info)) + # Find out which files were modified by the callbacks. Such paths could # lead to subsequent commits being empty (e.g. if removing a line containing # a password from every version of a file that had the password, and some @@ -3765,10 +3764,6 @@ class RepoFilter(object): differences = orig_file_changes.symmetric_difference(final_file_changes) self._files_tweaked.update(x.filename for x in differences) - # Call the user-defined callback, if any - if self._commit_callback: - self._commit_callback(commit, self.callback_metadata(aux_info)) - # Now print the resulting commit, or if prunable skip it if not commit.dumped: if not self._prunable(commit, new_1st_parent, @@ -3781,8 +3776,13 @@ class RepoFilter(object): if self._args.state_branch: alias = Alias(commit.old_id or commit.id, rewrite_to or deleted_hash) self._insert_into_stream(alias) - reset = Reset(commit.branch, rewrite_to or deleted_hash) - self._insert_into_stream(reset) + if commit.branch.startswith(b'refs/') or commit.branch == b'HEAD': + # The special check above is because when direct revisions are passed + # along to fast-export (such as with stashes), there is a chance the + # revision is rewritten to nothing. In such cases, we don't want to + # point an invalid ref that just names a revision to some other point. + reset = Reset(commit.branch, rewrite_to or deleted_hash) + self._insert_into_stream(reset) self._commit_renames[commit.original_id] = None # Show progress @@ -3933,21 +3933,26 @@ class RepoFilter(object): git_dir = GitUtils.determine_git_dir(repo_working_dir) stash = os.path.join(git_dir, b'logs', b'refs', b'stash') if os.path.exists(stash): + self._stash = [] with open(stash, 'br') as f: - self._stash = f.read() - out = subproc.check_output('git rev-list -g refs/stash'.split(), - cwd=repo_working_dir) - self._args.refs.extend(decode(out.strip()).split()) + for line in f: + (oldhash, newhash, rest) = line.split(None, 2) + self._stash.append((newhash, rest)) + self._args.refs.extend([x[0] for x in self._stash]) def _write_stash(self): + last = deleted_hash if self._stash: target_working_dir = self._args.target or b'.' git_dir = GitUtils.determine_git_dir(target_working_dir) stash = os.path.join(git_dir, b'logs', b'refs', b'stash') with open(stash, 'bw') as f: - self._stash = self._full_hash_re.sub(self._translate_full_commit_hash, - self._stash) - f.write(self._stash) + for (hash, rest) in self._stash: + new_hash = self._get_rename(hash) + if new_hash is None: + continue + f.write(b' '.join([last, new_hash, rest]) + b'\n') + last = new_hash print(_("Rewrote the stash.")) def _setup_input(self, use_done_feature): @@ -4083,6 +4088,10 @@ class RepoFilter(object): # Remove unused refs exported_refs, imported_refs = self.get_exported_and_imported_refs() refs_to_nuke = exported_refs - imported_refs + # Because revisions can be passed to fast-export which handles them as + # though they were refs, we might have bad "refs" to nuke; strip them out. + refs_to_nuke = [x for x in refs_to_nuke + if x.startswith(b'refs/') or x == b'HEAD'] if self._args.partial: refs_to_nuke = set() if refs_to_nuke and self._args.debug: @@ -4134,13 +4143,11 @@ class RepoFilter(object): return new_hash def _compute_metadata(self, metadata_dir, orig_refs): - already_ran = os.path.isfile(os.path.join(metadata_dir, b'already_ran')) - # # First, handle commit_renames # old_commit_renames = dict() - if not already_ran: + if not self._already_ran: commit_renames = {old: new for old, new in self._commit_renames.items() } @@ -4169,7 +4176,7 @@ class RepoFilter(object): exported_refs, imported_refs = self.get_exported_and_imported_refs() old_commit_unrenames = dict() - if not already_ran: + if not self._already_ran: old_ref_map = dict((refname, (old_hash, deleted_hash)) for refname, old_hash in orig_refs.items() if refname in exported_refs) @@ -4236,7 +4243,7 @@ class RepoFilter(object): # old_first_changes = dict() - if already_ran: + if self._already_ran: # Read first_changes into old_first_changes with open(os.path.join(metadata_dir, b'first-changed-commits'), 'br') as f: for line in f: diff --git a/t/t9390-filter-repo-basics.sh b/t/t9390-filter-repo-basics.sh new file mode 100755 index 00000000..a2bd0475 --- /dev/null +++ b/t/t9390-filter-repo-basics.sh @@ -0,0 +1,877 @@ +#!/bin/bash + +test_description='Basic filter-repo tests' + +. ./test-lib.sh + +export PATH=$(dirname $TEST_DIRECTORY):$PATH # Put git-filter-repo in PATH + +DATA="$TEST_DIRECTORY/t9390" +SQ="'" + +filter_testcase() { + INPUT=$1 + OUTPUT=$2 + shift 2 + REST=("$@") + + + NAME="check: $INPUT -> $OUTPUT using '${REST[@]}'" + test_expect_success "$NAME" ' + # Clean up from previous run + git pack-refs --all && + rm .git/packed-refs && + rm -rf .git/filter-repo/ && + + # Run the example + cat $DATA/$INPUT | git filter-repo --stdin --quiet --force --replace-refs delete-no-add "${REST[@]}" && + + # Compare the resulting repo to expected value + git fast-export --use-done-feature --all >compare && + test_cmp $DATA/$OUTPUT compare + ' +} + +filter_testcase basic basic-filename --path filename +filter_testcase basic basic-twenty --path twenty +filter_testcase basic basic-ten --path ten +filter_testcase basic basic-numbers --path ten --path twenty +filter_testcase basic basic-filename --invert-paths --path-glob 't*en*' +filter_testcase basic basic-numbers --invert-paths --path-regex 'f.*e.*e' +filter_testcase basic basic-mailmap --mailmap ../t9390/sample-mailmap +filter_testcase basic basic-replace --replace-text ../t9390/sample-replace +filter_testcase basic basic-message --replace-message ../t9390/sample-message +filter_testcase empty empty-keepme --path keepme +filter_testcase empty more-empty-keepme --path keepme --prune-empty=always \ + --prune-degenerate=always +filter_testcase empty less-empty-keepme --path keepme --prune-empty=never \ + --prune-degenerate=never +filter_testcase degenerate degenerate-keepme --path moduleA/keepme +filter_testcase degenerate degenerate-moduleA --path moduleA +filter_testcase degenerate degenerate-globme --path-glob *me +filter_testcase degenerate degenerate-keepme-noff --path moduleA/keepme --no-ff +filter_testcase unusual unusual-filtered --path '' +filter_testcase unusual unusual-mailmap --mailmap ../t9390/sample-mailmap + +setup_path_rename() { + test -d path_rename && return + test_create_repo path_rename && + ( + cd path_rename && + mkdir sequences values && + test_seq 1 10 >sequences/tiny && + test_seq 100 110 >sequences/intermediate && + test_seq 1000 1010 >sequences/large && + test_seq 1000 1010 >values/large && + test_seq 10000 10010 >values/huge && + git add sequences values && + git commit -m initial && + + git mv sequences/tiny sequences/small && + cp sequences/intermediate sequences/medium && + echo 10011 >values/huge && + git add sequences values && + git commit -m updates && + + git rm sequences/intermediate && + echo 11 >sequences/small && + git add sequences/small && + git commit -m changes && + + echo 1011 >sequences/medium && + git add sequences/medium && + git commit -m final + ) +} + +test_expect_success '--path-rename sequences/tiny:sequences/small' ' + setup_path_rename && + ( + git clone file://"$(pwd)"/path_rename path_rename_single && + cd path_rename_single && + git filter-repo --path-rename sequences/tiny:sequences/small && + git log --format=%n --name-only | sort | uniq >filenames && + test_line_count = 7 filenames && + ! grep sequences/tiny filenames && + git rev-parse HEAD~3:sequences/small + ) +' + +test_expect_success '--path-rename sequences:numbers' ' + setup_path_rename && + ( + git clone file://"$(pwd)"/path_rename path_rename_dir && + cd path_rename_dir && + git filter-repo --path-rename sequences:numbers && + git log --format=%n --name-only | sort | uniq >filenames && + test_line_count = 8 filenames && + ! grep sequences/ filenames && + grep numbers/ filenames && + grep values/ filenames + ) +' + +test_expect_success '--path-rename-prefix values:numbers' ' + setup_path_rename && + ( + git clone file://"$(pwd)"/path_rename path_rename_dir_2 && + cd path_rename_dir_2 && + git filter-repo --path-rename values/:numbers/ && + git log --format=%n --name-only | sort | uniq >filenames && + test_line_count = 8 filenames && + ! grep values/ filenames && + grep sequences/ filenames && + grep numbers/ filenames + ) +' + +test_expect_success '--path-rename squashing' ' + setup_path_rename && + ( + git clone file://"$(pwd)"/path_rename path_rename_squash && + cd path_rename_squash && + git filter-repo \ + --path-rename sequences/tiny:sequences/small \ + --path-rename sequences:numbers \ + --path-rename values:numbers \ + --path-rename numbers/intermediate:numbers/medium && + git log --format=%n --name-only | sort | uniq >filenames && + # Just small, medium, large, huge, and a blank line... + test_line_count = 5 filenames && + ! grep sequences/ filenames && + ! grep values/ filenames && + grep numbers/ filenames + ) +' + +test_expect_success '--path-rename inability to squash' ' + setup_path_rename && + ( + git clone file://"$(pwd)"/path_rename path_rename_bad_squash && + cd path_rename_bad_squash && + test_must_fail git filter-repo \ + --path-rename values/large:values/big \ + --path-rename values/huge:values/big 2>../err && + test_i18ngrep "File renaming caused colliding pathnames" ../err + ) +' + +test_expect_success '--paths-from-file' ' + setup_path_rename && + ( + git clone file://"$(pwd)"/path_rename paths_from_file && + cd paths_from_file && + + cat >../path_changes <<-EOF && + literal:values/huge + values/huge==>values/gargantuan + glob:*rge + + # Comments and blank lines are ignored + regex:.*med.* + regex:^([^/]*)/(.*)ge$==>\2/\1/ge + EOF + + git filter-repo --paths-from-file ../path_changes && + git log --format=%n --name-only | sort | uniq >filenames && + # intermediate, medium, two larges, gargantuan, and a blank line + test_line_count = 6 filenames && + ! grep sequences/tiny filenames && + grep sequences/intermediate filenames && + grep lar/sequences/ge filenames && + grep lar/values/ge filenames && + grep values/gargantuan filenames && + ! grep sequences/small filenames && + grep sequences/medium filenames && + + rm ../path_changes + ) +' + +test_expect_success '--paths does not mean --paths-from-file' ' + setup_path_rename && + ( + git clone file://"$(pwd)"/path_rename paths_misuse && + cd paths_misuse && + + test_must_fail git filter-repo --paths values/large 2>../err && + + grep "Error: Option.*--paths.*unrecognized; did you" ../err && + rm ../err + ) +' + +create_path_filtering_and_renaming() { + test -d path_filtering_and_renaming && return + + test_create_repo path_filtering_and_renaming && + ( + cd path_filtering_and_renaming && + + >.gitignore && + mkdir -p src/main/java/com/org/{foo,bar} && + mkdir -p src/main/resources && + test_seq 1 10 >src/main/java/com/org/foo/uptoten && + test_seq 11 20 >src/main/java/com/org/bar/uptotwenty && + test_seq 1 7 >src/main/java/com/org/uptoseven && + test_seq 1 5 >src/main/resources/uptofive && + git add . && + git commit -m Initial + ) +} + +test_expect_success 'Mixing filtering and renaming paths, not enough filters' ' + create_path_filtering_and_renaming && + git clone --no-local path_filtering_and_renaming \ + path_filtering_and_renaming_1 && + ( + cd path_filtering_and_renaming_1 && + + git filter-repo --path .gitignore \ + --path src/main/resources \ + --path-rename src/main/java/com/org/foo/:src/main/java/com/org/ && + + cat <<-EOF >expect && + .gitignore + src/main/resources/uptofive + EOF + git ls-files >actual && + test_cmp expect actual + ) +' + +test_expect_success 'Mixing filtering and renaming paths, enough filters' ' + create_path_filtering_and_renaming && + git clone --no-local path_filtering_and_renaming \ + path_filtering_and_renaming_2 && + ( + cd path_filtering_and_renaming_2 && + + git filter-repo --path .gitignore \ + --path src/main/resources \ + --path src/main/java/com/org/foo/ \ + --path-rename src/main/java/com/org/foo/:src/main/java/com/org/ && + + cat <<-EOF >expect && + .gitignore + src/main/java/com/org/uptoten + src/main/resources/uptofive + EOF + git ls-files >actual && + test_cmp expect actual + ) +' + +test_expect_success 'Mixing filtering and to-subdirectory-filter' ' + create_path_filtering_and_renaming && + git clone --no-local path_filtering_and_renaming \ + path_filtering_and_renaming_3 && + ( + cd path_filtering_and_renaming_3 && + + git filter-repo --path src/main/resources \ + --to-subdirectory-filter my-module && + + cat <<-EOF >expect && + my-module/src/main/resources/uptofive + EOF + git ls-files >actual && + test_cmp expect actual + ) +' + +setup_commit_message_rewriting() { + test -d commit_msg && return + test_create_repo commit_msg && + ( + cd commit_msg && + echo two guys walking into a >bar && + git add bar && + git commit -m initial && + + test_commit another && + + name=$(git rev-parse HEAD) && + echo hello >world && + git add world && + git commit -m "Commit referencing ${name:0:8}" && + + git revert HEAD && + + for i in $(test_seq 1 200) + do + git commit --allow-empty -m "another commit" + done && + + echo foo >bar && + git add bar && + git commit -m bar && + + git revert --no-commit HEAD && + echo foo >baz && + git add baz && + git commit + ) +} + +test_expect_success 'commit message rewrite' ' + setup_commit_message_rewriting && + ( + git clone file://"$(pwd)"/commit_msg commit_msg_clone && + cd commit_msg_clone && + + git filter-repo --invert-paths --path bar && + + git log --oneline >changes && + test_line_count = 204 changes && + + # If a commit we reference is rewritten, we expect the + # reference to be rewritten. + name=$(git rev-parse HEAD~203) && + echo "Commit referencing ${name:0:8}" >expect && + git log --no-walk --format=%s HEAD~202 >actual && + test_cmp expect actual && + + # If a commit we reference was pruned, then the reference + # has nothing to be rewritten to. Verify that the commit + # ID it points to does not exist. + latest=$(git log --no-walk | grep reverts | awk "{print \$4}" | tr -d '.') && + test -n "$latest" && + test_must_fail git cat-file -e "$latest" + ) +' + +test_expect_success 'commit hash unchanged if requested' ' + setup_commit_message_rewriting && + ( + git clone file://"$(pwd)"/commit_msg commit_msg_clone_2 && + cd commit_msg_clone_2 && + + name=$(git rev-parse HEAD~204) && + git filter-repo --invert-paths --path bar --preserve-commit-hashes && + + git log --oneline >changes && + test_line_count = 204 changes && + + echo "Commit referencing ${name:0:8}" >expect && + git log --no-walk --format=%s HEAD~202 >actual && + test_cmp expect actual + ) +' + +test_expect_success 'commit message encoding preserved if requested' ' + ( + git init commit_message_encoding && + cd commit_message_encoding && + + cat >input <<-\EOF && + feature done + commit refs/heads/develop + mark :1 + original-oid deadbeefdeadbeefdeadbeefdeadbeefdeadbeef + author Just Me 1234567890 -0200 + committer Just Me 1234567890 -0200 + encoding iso-8859-7 + data 5 + EOF + + printf "Pi: \360\n\ndone\n" >>input && + + cat input | git fast-import --quiet && + git rev-parse develop >expect && + + git filter-repo --preserve-commit-encoding --force && + git rev-parse develop >actual && + test_cmp expect actual + ) +' + +test_expect_success 'commit message rewrite unsuccessful' ' + ( + git init commit_msg_not_found && + cd commit_msg_not_found && + + cat >input <<-\EOF && + feature done + commit refs/heads/develop + mark :1 + original-oid deadbeefdeadbeefdeadbeefdeadbeefdeadbeef + author Just Me 1234567890 -0200 + committer Just Me 1234567890 -0200 + data 2 + A + + commit refs/heads/develop + mark :2 + original-oid deadbeefcafedeadbeefcafedeadbeefcafecafe + author Just Me 1234567890 -0200 + committer Just Me 1234567890 -0200 + data 2 + B + + commit refs/heads/develop + mark :3 + original-oid 0000000000000000000000000000000000000004 + author Just Me 3980014290 -0200 + committer Just Me 3980014290 -0200 + data 93 + Four score and seven years ago, commit deadbeef ("B", + 2009-02-13) messed up. This fixes it. + done + EOF + + cat input | git filter-repo --stdin --path salutation --force && + + git log --oneline develop >changes && + test_line_count = 3 changes && + + git log develop >out && + grep deadbeef out + ) +' + +test_expect_success '--refs and --replace-text' ' + # This test exists to make sure we do not assume that parents in + # filter-repo code are always represented by integers (or marks); + # they sometimes are represented as hashes. + setup_path_rename && + ( + git clone file://"$(pwd)"/path_rename refs_and_replace_text && + cd refs_and_replace_text && + git rev-parse --short=10 HEAD~1 >myparent && + echo "10==>TEN" >input && + git filter-repo --force --replace-text input --refs $(cat myparent)..master && + cat <<-EOF >expect && + TEN11 + EOF + test_cmp expect sequences/medium && + git rev-list --count HEAD >actual && + echo 4 >expect && + test_cmp expect actual && + git rev-parse --short=10 HEAD~1 >actual && + test_cmp myparent actual + ) +' + +test_expect_success 'reset to specific refs' ' + test_create_repo reset_to_specific_refs && + ( + cd reset_to_specific_refs && + + git commit --allow-empty -m initial && + INITIAL=$(git rev-parse HEAD) && + echo "$INITIAL refs/heads/develop" >expect && + + cat >input <<-INPUT_END && + reset refs/heads/develop + from $INITIAL + + reset refs/heads/master + from 0000000000000000000000000000000000000000 + INPUT_END + + cat input | git filter-repo --force --stdin && + git show-ref >actual && + test_cmp expect actual + ) +' + +setup_handle_funny_characters() { + test -d funny_chars && return + test_create_repo funny_chars && + ( + cd funny_chars && + + git symbolic-ref HEAD refs/heads/españa && + + printf "بتتكلم بالهندي؟\n" >señor && + printf "Αυτά μου φαίνονται αλαμπουρνέζικα.\n" >>señor && + printf "זה סינית בשבילי\n" >>señor && + printf "ちんぷんかんぷん\n" >>señor && + printf "За мене тоа е шпанско село\n" >>señor && + printf "看起来像天书。\n" >>señor && + printf "انگار ژاپنی حرف می زنه\n" >>señor && + printf "Это для меня китайская грамота.\n" >>señor && + printf "To mi je španska vas\n" >>señor && + printf "Konuya Fransız kaldım\n" >>señor && + printf "עס איז די שפּראַך פון גיבבעריש\n" >>señor && + printf "Not even UTF-8:\xe0\x80\x80\x00\n" >>señor && + + cp señor señora && + cp señor señorita && + git add . && + + export GIT_AUTHOR_NAME="Nguyễn Arnfjörð Gábor" && + export GIT_COMMITTER_NAME=$GIT_AUTHOR_NAME && + export GIT_AUTHOR_EMAIL="emails@are.ascii" && + export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL" && + git commit -m "€$£₽₪" && + + git tag -a -m "₪₽£€$" סְפָרַד + ) +} + +test_expect_success 'handle funny characters' ' + setup_handle_funny_characters && + ( + git clone file://"$(pwd)"/funny_chars funny_chars_checks && + cd funny_chars_checks && + + file_sha=$(git rev-parse :0:señor) && + former_head_sha=$(git rev-parse HEAD) && + git filter-repo --replace-refs old-default --to-subdirectory-filter títulos && + + cat <<-EOF >expect && + 100644 $file_sha 0 "t\303\255tulos/se\303\261or" + 100644 $file_sha 0 "t\303\255tulos/se\303\261ora" + 100644 $file_sha 0 "t\303\255tulos/se\303\261orita" + EOF + + git ls-files -s >actual && + test_cmp expect actual && + + commit_sha=$(git rev-parse HEAD) && + tag_sha=$(git rev-parse סְפָרַד) && + cat <<-EOF >expect && + $commit_sha refs/heads/españa + $commit_sha refs/replace/$former_head_sha + $tag_sha refs/tags/סְפָרַד + EOF + + git show-ref >actual && + test_cmp expect actual && + + echo "€$£₽₪" >expect && + git cat-file -p HEAD | tail -n 1 >actual && + + echo "₪₽£€$" >expect && + git cat-file -p סְפָרַד | tail -n 1 >actual + ) +' + +test_expect_success '--state-branch with changing renames' ' + test_create_repo state_branch_renames_export + test_create_repo state_branch_renames && + ( + cd state_branch_renames && + git fast-import --quiet <$DATA/basic-numbers && + git branch -d A && + git branch -d B && + git tag -d v1.0 && + + ORIG=$(git rev-parse master) && + git reset --hard master~1 && + git filter-repo --path-rename ten:zehn \ + --state-branch state_info \ + --target ../state_branch_renames_export && + + cd ../state_branch_renames_export && + git log --format=%s --name-status >actual && + cat <<-EOF >expect && + Merge branch ${SQ}A${SQ} into B + add twenty + + M twenty + add ten + + M zehn + Initial + + A twenty + A zehn + EOF + test_cmp expect actual && + + cd ../state_branch_renames && + + git reset --hard $ORIG && + git filter-repo --path-rename twenty:veinte \ + --state-branch state_info \ + --target ../state_branch_renames_export && + + cd ../state_branch_renames_export && + git log --format=%s --name-status >actual && + cat <<-EOF >expect && + whatever + + A ten + A veinte + Merge branch ${SQ}A${SQ} into B + add twenty + + M twenty + add ten + + M zehn + Initial + + A twenty + A zehn + EOF + test_cmp expect actual + ) +' + +test_expect_success '--state-branch with expanding paths and refs' ' + test_create_repo state_branch_more_paths_export + test_create_repo state_branch_more_paths && + ( + cd state_branch_more_paths && + git fast-import --quiet <$DATA/basic-numbers && + + git reset --hard master~1 && + git filter-repo --path ten --state-branch state_info \ + --target ../state_branch_more_paths_export \ + --refs master && + + cd ../state_branch_more_paths_export && + echo 2 >expect && + git rev-list --count master >actual && + test_cmp expect actual && + test_must_fail git rev-parse master~1:twenty && + test_must_fail git rev-parse master:twenty && + + cd ../state_branch_more_paths && + + git reset --hard v1.0 && + git filter-repo --path ten --path twenty \ + --state-branch state_info \ + --target ../state_branch_more_paths_export && + + cd ../state_branch_more_paths_export && + echo 3 >expect && + git rev-list --count master >actual && + test_cmp expect actual && + test_must_fail git rev-parse master~2:twenty && + git rev-parse master:twenty + ) +' + +test_expect_success FUNNYNAMES 'degenerate merge with non-matching filenames' ' + test_create_repo degenerate_merge_differing_filenames && + ( + cd degenerate_merge_differing_filenames && + + touch "foo \"quote\" bar" && + git add "foo \"quote\" bar" && + git commit -m "Add foo \"quote\" bar" + git branch A && + + git checkout --orphan B && + git reset --hard && + mkdir -p pkg/list && + test_commit pkg/list/whatever && + test_commit unwanted_file && + + git checkout A && + git merge --allow-unrelated-histories --no-commit B && + >pkg/list/wanted && + git add pkg/list/wanted && + git rm -f pkg/list/whatever.t && + git commit && + + git filter-repo --force --path pkg/list && + ! test_path_is_file pkg/list/whatever.t && + git ls-files >actual && + echo pkg/list/wanted >expect && + test_cmp expect actual + ) +' + +test_expect_success 'degenerate merge with typechange' ' + test_create_repo degenerate_merge_with_typechange && + ( + cd degenerate_merge_with_typechange && + + touch irrelevant_file && + git add irrelevant_file && + git commit -m "Irrelevant, unwanted file" + git branch A && + + git checkout --orphan B && + git reset --hard && + echo hello >world && + git add world && + git commit -m "greeting" && + echo goodbye >planet && + git add planet && + git commit -m "farewell" && + + git checkout A && + git merge --allow-unrelated-histories --no-commit B && + rm world && + ln -s planet world && + git add world && + git commit && + + git filter-repo --force --path world && + test_path_is_missing irrelevant_file && + test_path_is_missing planet && + echo world >expect && + git ls-files >actual && + test_cmp expect actual && + + git log --oneline HEAD >input && + test_line_count = 2 input + ) +' + +test_expect_success 'degenerate evil merge' ' + test_create_repo degenerate_evil_merge && + ( + cd degenerate_evil_merge && + + cat $DATA/degenerate-evil-merge | git fast-import --quiet && + git filter-repo --force --subdirectory-filter module-of-interest && + test_path_is_missing module-of-interest && + test_path_is_missing other-module && + test_path_is_missing irrelevant && + test_path_is_file file1 && + test_path_is_file file2 && + test_path_is_file file3 + ) +' + +test_lazy_prereq IN_FILTER_REPO_CLONE ' + git -C ../../ rev-parse HEAD:git-filter-repo && + grep @@LOCALEDIR@@ ../../../git-filter-repo && + head -n 1 ../../../git-filter-repo | grep "/usr/bin/env python3$" +' + +# Next test depends on git-filter-repo coming from the git-filter-repo +# not having been modified by e.g. normal installation. Skip the test +# if we're in some kind of installation of filter-repo rather than in a +# simple clone of the original repository. +test_expect_success IN_FILTER_REPO_CLONE '--version' ' + git filter-repo --version >actual && + git hash-object ../../git-filter-repo | cut -c 1-12 >expect && + test_cmp expect actual +' + +test_expect_success 'empty author ident' ' + test_create_repo empty_author_ident && + ( + cd empty_author_ident && + + git init && + cat <<-EOF | git fast-import --quiet && + feature done + blob + mark :1 + data 8 + initial + + reset refs/heads/develop + commit refs/heads/develop + mark :2 + author 1535228562 -0700 + committer Full Name 1535228562 -0700 + data 8 + Initial + M 100644 :1 filename + + done + EOF + + git filter-repo --force --path-rename filename:stuff && + + git log --format=%an develop >actual && + echo >expect && + test_cmp expect actual + ) +' + +test_expect_success 'rewrite stash' ' + test_create_repo rewrite_stash && + ( + cd rewrite_stash && + + git init && + test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers && + git add numbers && + git commit -qm initial && + + echo 11 >>numbers && + git stash push -m "add eleven" && + echo foobar >>numbers && + git stash push -m "add foobar" && + + git filter-repo --force --path-rename numbers:values && + + git stash list >output && + test 2 -eq $(cat output | wc -l) + ) +' + +test_expect_success 'rewrite stash and drop relevant entries' ' + test_create_repo rewrite_stash_drop_entries && + ( + cd rewrite_stash_drop_entries && + + git init && + test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers && + git add numbers && + git commit -qm numbers && + + echo 11 >>numbers && + git stash push -m "add eleven" && + + test_write_lines a b c d e f g h i j >letters && + test_write_lines hello hi welcome >greetings && + git add letters greetings && + git commit -qm "letters and greetings" && + + echo k >>letters && + git stash push -m "add k" && + echo hey >>greetings && + git stash push -m "add hey" && + echo 12 >>numbers && + git stash push -m "add twelve" && + + test_line_count = 4 .git/logs/refs/stash && + + git filter-repo --force --path letters --path greetings && + + test_line_count = 3 .git/logs/refs/stash && + ! grep add.eleven .git/logs/refs/stash && + grep add.k .git/logs/refs/stash && + grep add.hey .git/logs/refs/stash && + grep add.twelve .git/logs/refs/stash + ) +' + +test_expect_success POSIXPERM 'failure to run cleanup' ' + test_create_repo fail_to_cleanup && + ( + cd fail_to_cleanup && + + git init && + test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers && + git add numbers && + git commit -qm initial && + + chmod u-w .git/logs && + test_must_fail git filter-repo --force \ + --path-rename numbers:values 2> ../err && + chmod u+w .git/logs && + grep fatal.*git.reflog.expire.*failed ../err + ) +' + +test_expect_success 'origin refs without origin remote does not die' ' + test_create_repo origin_refs_with_origin_remote && + ( + cd origin_refs_with_origin_remote && + + test_commit numbers && + git update-ref refs/remotes/origin/svnhead master && + + git filter-repo --force --path-rename numbers.t:values.t && + + git show svnhead:values.t >actual && + echo numbers >expect && + test_cmp expect actual + ) +' + +test_done diff --git a/t/t9392-python-callback.sh b/t/t9392-filter-repo-python-callback.sh similarity index 86% rename from t/t9392-python-callback.sh rename to t/t9392-filter-repo-python-callback.sh index cb36292a..1f8be45f 100755 --- a/t/t9392-python-callback.sh +++ b/t/t9392-filter-repo-python-callback.sh @@ -192,5 +192,39 @@ test_expect_success 'Callback read from a file' ' ) ' +test_expect_success 'Filtering a blob to make it match previous version' ' + test_create_repo remove_unique_bits_of_blob && + ( + cd remove_unique_bits_of_blob && + + test_write_lines foo baz >metasyntactic_names && + git add metasyntactic_names && + git commit -m init && + + test_write_lines foo bar baz >metasyntactic_names && + git add metasyntactic_names && + git commit -m second && + + git filter-repo --force --blob-callback "blob.data = blob.data.replace(b\"\\nbar\", b\"\")" + + echo 1 >expect && + git rev-list --count HEAD >actual && + test_cmp expect actual + ) +' + +test_expect_success 'tweaking just a tag' ' + test_create_repo tweaking_just_a_tag && + ( + cd tweaking_just_a_tag && + + test_commit foo && + git tag -a -m "Here is a tag" mytag && + + git filter-repo --force --refs mytag ^mytag^{commit} --name-callback "return name.replace(b\"Mitter\", b\"L D\")" && + + git cat-file -p mytag | grep C.O.L.D + ) +' test_done diff --git a/t/t9393-rerun.sh b/t/t9393-filter-repo-rerun.sh similarity index 100% rename from t/t9393-rerun.sh rename to t/t9393-filter-repo-rerun.sh diff --git a/t/t9390-filter-repo.sh b/t/t9394-filter-repo-sanity-checks-and-bigger-repo-setup.sh similarity index 60% rename from t/t9390-filter-repo.sh rename to t/t9394-filter-repo-sanity-checks-and-bigger-repo-setup.sh index 4a54067f..82e107b5 100755 --- a/t/t9390-filter-repo.sh +++ b/t/t9394-filter-repo-sanity-checks-and-bigger-repo-setup.sh @@ -6,279 +6,7 @@ test_description='Basic filter-repo tests' export PATH=$(dirname $TEST_DIRECTORY):$PATH # Put git-filter-repo in PATH -DATA="$TEST_DIRECTORY/t9390" -SQ="'" - -filter_testcase() { - INPUT=$1 - OUTPUT=$2 - shift 2 - REST=("$@") - - - NAME="check: $INPUT -> $OUTPUT using '${REST[@]}'" - test_expect_success "$NAME" ' - # Clean up from previous run - git pack-refs --all && - rm .git/packed-refs && - rm -rf .git/filter-repo/ && - - # Run the example - cat $DATA/$INPUT | git filter-repo --stdin --quiet --force --replace-refs delete-no-add "${REST[@]}" && - - # Compare the resulting repo to expected value - git fast-export --use-done-feature --all >compare && - test_cmp $DATA/$OUTPUT compare - ' -} - -filter_testcase basic basic-filename --path filename -filter_testcase basic basic-twenty --path twenty -filter_testcase basic basic-ten --path ten -filter_testcase basic basic-numbers --path ten --path twenty -filter_testcase basic basic-filename --invert-paths --path-glob 't*en*' -filter_testcase basic basic-numbers --invert-paths --path-regex 'f.*e.*e' -filter_testcase basic basic-mailmap --mailmap ../t9390/sample-mailmap -filter_testcase basic basic-replace --replace-text ../t9390/sample-replace -filter_testcase basic basic-message --replace-message ../t9390/sample-message -filter_testcase empty empty-keepme --path keepme -filter_testcase empty more-empty-keepme --path keepme --prune-empty=always \ - --prune-degenerate=always -filter_testcase empty less-empty-keepme --path keepme --prune-empty=never \ - --prune-degenerate=never -filter_testcase degenerate degenerate-keepme --path moduleA/keepme -filter_testcase degenerate degenerate-moduleA --path moduleA -filter_testcase degenerate degenerate-globme --path-glob *me -filter_testcase degenerate degenerate-keepme-noff --path moduleA/keepme --no-ff -filter_testcase unusual unusual-filtered --path '' -filter_testcase unusual unusual-mailmap --mailmap ../t9390/sample-mailmap - -setup_path_rename() { - test -d path_rename && return - test_create_repo path_rename && - ( - cd path_rename && - mkdir sequences values && - test_seq 1 10 >sequences/tiny && - test_seq 100 110 >sequences/intermediate && - test_seq 1000 1010 >sequences/large && - test_seq 1000 1010 >values/large && - test_seq 10000 10010 >values/huge && - git add sequences values && - git commit -m initial && - - git mv sequences/tiny sequences/small && - cp sequences/intermediate sequences/medium && - echo 10011 >values/huge && - git add sequences values && - git commit -m updates && - - git rm sequences/intermediate && - echo 11 >sequences/small && - git add sequences/small && - git commit -m changes && - - echo 1011 >sequences/medium && - git add sequences/medium && - git commit -m final - ) -} - -test_expect_success '--path-rename sequences/tiny:sequences/small' ' - setup_path_rename && - ( - git clone file://"$(pwd)"/path_rename path_rename_single && - cd path_rename_single && - git filter-repo --path-rename sequences/tiny:sequences/small && - git log --format=%n --name-only | sort | uniq >filenames && - test_line_count = 7 filenames && - ! grep sequences/tiny filenames && - git rev-parse HEAD~3:sequences/small - ) -' - -test_expect_success '--path-rename sequences:numbers' ' - setup_path_rename && - ( - git clone file://"$(pwd)"/path_rename path_rename_dir && - cd path_rename_dir && - git filter-repo --path-rename sequences:numbers && - git log --format=%n --name-only | sort | uniq >filenames && - test_line_count = 8 filenames && - ! grep sequences/ filenames && - grep numbers/ filenames && - grep values/ filenames - ) -' - -test_expect_success '--path-rename-prefix values:numbers' ' - setup_path_rename && - ( - git clone file://"$(pwd)"/path_rename path_rename_dir_2 && - cd path_rename_dir_2 && - git filter-repo --path-rename values/:numbers/ && - git log --format=%n --name-only | sort | uniq >filenames && - test_line_count = 8 filenames && - ! grep values/ filenames && - grep sequences/ filenames && - grep numbers/ filenames - ) -' - -test_expect_success '--path-rename squashing' ' - setup_path_rename && - ( - git clone file://"$(pwd)"/path_rename path_rename_squash && - cd path_rename_squash && - git filter-repo \ - --path-rename sequences/tiny:sequences/small \ - --path-rename sequences:numbers \ - --path-rename values:numbers \ - --path-rename numbers/intermediate:numbers/medium && - git log --format=%n --name-only | sort | uniq >filenames && - # Just small, medium, large, huge, and a blank line... - test_line_count = 5 filenames && - ! grep sequences/ filenames && - ! grep values/ filenames && - grep numbers/ filenames - ) -' - -test_expect_success '--path-rename inability to squash' ' - setup_path_rename && - ( - git clone file://"$(pwd)"/path_rename path_rename_bad_squash && - cd path_rename_bad_squash && - test_must_fail git filter-repo \ - --path-rename values/large:values/big \ - --path-rename values/huge:values/big 2>../err && - test_i18ngrep "File renaming caused colliding pathnames" ../err - ) -' - -test_expect_success '--paths-from-file' ' - setup_path_rename && - ( - git clone file://"$(pwd)"/path_rename paths_from_file && - cd paths_from_file && - - cat >../path_changes <<-EOF && - literal:values/huge - values/huge==>values/gargantuan - glob:*rge - - # Comments and blank lines are ignored - regex:.*med.* - regex:^([^/]*)/(.*)ge$==>\2/\1/ge - EOF - - git filter-repo --paths-from-file ../path_changes && - git log --format=%n --name-only | sort | uniq >filenames && - # intermediate, medium, two larges, gargantuan, and a blank line - test_line_count = 6 filenames && - ! grep sequences/tiny filenames && - grep sequences/intermediate filenames && - grep lar/sequences/ge filenames && - grep lar/values/ge filenames && - grep values/gargantuan filenames && - ! grep sequences/small filenames && - grep sequences/medium filenames && - - rm ../path_changes - ) -' - -test_expect_success '--paths does not mean --paths-from-file' ' - setup_path_rename && - ( - git clone file://"$(pwd)"/path_rename paths_misuse && - cd paths_misuse && - - test_must_fail git filter-repo --paths values/large 2>../err && - - grep "Error: Option.*--paths.*unrecognized; did you" ../err && - rm ../err - ) -' - -create_path_filtering_and_renaming() { - test -d path_filtering_and_renaming && return - - test_create_repo path_filtering_and_renaming && - ( - cd path_filtering_and_renaming && - - >.gitignore && - mkdir -p src/main/java/com/org/{foo,bar} && - mkdir -p src/main/resources && - test_seq 1 10 >src/main/java/com/org/foo/uptoten && - test_seq 11 20 >src/main/java/com/org/bar/uptotwenty && - test_seq 1 7 >src/main/java/com/org/uptoseven && - test_seq 1 5 >src/main/resources/uptofive && - git add . && - git commit -m Initial - ) -} - -test_expect_success 'Mixing filtering and renaming paths, not enough filters' ' - create_path_filtering_and_renaming && - git clone --no-local path_filtering_and_renaming \ - path_filtering_and_renaming_1 && - ( - cd path_filtering_and_renaming_1 && - - git filter-repo --path .gitignore \ - --path src/main/resources \ - --path-rename src/main/java/com/org/foo/:src/main/java/com/org/ && - - cat <<-EOF >expect && - .gitignore - src/main/resources/uptofive - EOF - git ls-files >actual && - test_cmp expect actual - ) -' - -test_expect_success 'Mixing filtering and renaming paths, enough filters' ' - create_path_filtering_and_renaming && - git clone --no-local path_filtering_and_renaming \ - path_filtering_and_renaming_2 && - ( - cd path_filtering_and_renaming_2 && - - git filter-repo --path .gitignore \ - --path src/main/resources \ - --path src/main/java/com/org/foo/ \ - --path-rename src/main/java/com/org/foo/:src/main/java/com/org/ && - - cat <<-EOF >expect && - .gitignore - src/main/java/com/org/uptoten - src/main/resources/uptofive - EOF - git ls-files >actual && - test_cmp expect actual - ) -' - -test_expect_success 'Mixing filtering and to-subdirectory-filter' ' - create_path_filtering_and_renaming && - git clone --no-local path_filtering_and_renaming \ - path_filtering_and_renaming_3 && - ( - cd path_filtering_and_renaming_3 && - - git filter-repo --path src/main/resources \ - --to-subdirectory-filter my-module && - - cat <<-EOF >expect && - my-module/src/main/resources/uptofive - EOF - git ls-files >actual && - test_cmp expect actual - ) -' +DATA="$TEST_DIRECTORY/t9394" setup_metasyntactic_repo() { test -d metasyntactic && return @@ -761,9 +489,9 @@ test_expect_success C_LOCALE_OUTPUT '--analyze' ' # Detect whether zlib or zlib-ng are in use; they give # slightly different compression echo e80fdf8cd5fb645649c14f41656a076dedc4e12a >expect && - python -c "print(\"test\\t\" * 1000, end=\"\")" | git hash-object -w --stdin >actual && + python3 -c "print(\"test\\t\" * 1000, end=\"\")" | git hash-object -w --stdin >actual && test_cmp expect actual && - compressed_size=$(python -c "import os; print(os.path.getsize(\".git/objects/e8/0fdf8cd5fb645649c14f41656a076dedc4e12a\"))") && + compressed_size=$(python3 -c "import os; print(os.path.getsize(\".git/objects/e8/0fdf8cd5fb645649c14f41656a076dedc4e12a\"))") && zlibng=$((72-${compressed_size})) && test $zlibng -eq "0" -o $zlibng -eq "2" && @@ -1116,156 +844,6 @@ test_expect_success '--strip-blobs-with-ids' ' ) ' -setup_commit_message_rewriting() { - test -d commit_msg && return - test_create_repo commit_msg && - ( - cd commit_msg && - echo two guys walking into a >bar && - git add bar && - git commit -m initial && - - test_commit another && - - name=$(git rev-parse HEAD) && - echo hello >world && - git add world && - git commit -m "Commit referencing ${name:0:8}" && - - git revert HEAD && - - for i in $(test_seq 1 200) - do - git commit --allow-empty -m "another commit" - done && - - echo foo >bar && - git add bar && - git commit -m bar && - - git revert --no-commit HEAD && - echo foo >baz && - git add baz && - git commit - ) -} - -test_expect_success 'commit message rewrite' ' - setup_commit_message_rewriting && - ( - git clone file://"$(pwd)"/commit_msg commit_msg_clone && - cd commit_msg_clone && - - git filter-repo --invert-paths --path bar && - - git log --oneline >changes && - test_line_count = 204 changes && - - # If a commit we reference is rewritten, we expect the - # reference to be rewritten. - name=$(git rev-parse HEAD~203) && - echo "Commit referencing ${name:0:8}" >expect && - git log --no-walk --format=%s HEAD~202 >actual && - test_cmp expect actual && - - # If a commit we reference was pruned, then the reference - # has nothing to be rewritten to. Verify that the commit - # ID it points to does not exist. - latest=$(git log --no-walk | grep reverts | awk "{print \$4}" | tr -d '.') && - test -n "$latest" && - test_must_fail git cat-file -e "$latest" - ) -' - -test_expect_success 'commit hash unchanged if requested' ' - setup_commit_message_rewriting && - ( - git clone file://"$(pwd)"/commit_msg commit_msg_clone_2 && - cd commit_msg_clone_2 && - - name=$(git rev-parse HEAD~204) && - git filter-repo --invert-paths --path bar --preserve-commit-hashes && - - git log --oneline >changes && - test_line_count = 204 changes && - - echo "Commit referencing ${name:0:8}" >expect && - git log --no-walk --format=%s HEAD~202 >actual && - test_cmp expect actual - ) -' - -test_expect_success 'commit message encoding preserved if requested' ' - ( - git init commit_message_encoding && - cd commit_message_encoding && - - cat >input <<-\EOF && - feature done - commit refs/heads/develop - mark :1 - original-oid deadbeefdeadbeefdeadbeefdeadbeefdeadbeef - author Just Me 1234567890 -0200 - committer Just Me 1234567890 -0200 - encoding iso-8859-7 - data 5 - EOF - - printf "Pi: \360\n\ndone\n" >>input && - - cat input | git fast-import --quiet && - git rev-parse develop >expect && - - git filter-repo --preserve-commit-encoding --force && - git rev-parse develop >actual && - test_cmp expect actual - ) -' - -test_expect_success 'commit message rewrite unsuccessful' ' - ( - git init commit_msg_not_found && - cd commit_msg_not_found && - - cat >input <<-\EOF && - feature done - commit refs/heads/develop - mark :1 - original-oid deadbeefdeadbeefdeadbeefdeadbeefdeadbeef - author Just Me 1234567890 -0200 - committer Just Me 1234567890 -0200 - data 2 - A - - commit refs/heads/develop - mark :2 - original-oid deadbeefcafedeadbeefcafedeadbeefcafecafe - author Just Me 1234567890 -0200 - committer Just Me 1234567890 -0200 - data 2 - B - - commit refs/heads/develop - mark :3 - original-oid 0000000000000000000000000000000000000004 - author Just Me 3980014290 -0200 - committer Just Me 3980014290 -0200 - data 93 - Four score and seven years ago, commit deadbeef ("B", - 2009-02-13) messed up. This fixes it. - done - EOF - - cat input | git filter-repo --stdin --path salutation --force && - - git log --oneline develop >changes && - test_line_count = 3 changes && - - git log develop >out && - grep deadbeef out - ) -' - test_expect_success 'startup sanity checks' ' setup_analyze_me && ( @@ -1589,446 +1167,4 @@ test_expect_success '--refs' ' test_cmp refs/expect refs/actual ' -test_expect_success '--refs and --replace-text' ' - # This test exists to make sure we do not assume that parents in - # filter-repo code are always represented by integers (or marks); - # they sometimes are represented as hashes. - setup_path_rename && - ( - git clone file://"$(pwd)"/path_rename refs_and_replace_text && - cd refs_and_replace_text && - git rev-parse --short=10 HEAD~1 >myparent && - echo "10==>TEN" >input && - git filter-repo --force --replace-text input --refs $(cat myparent)..master && - cat <<-EOF >expect && - TEN11 - EOF - test_cmp expect sequences/medium && - git rev-list --count HEAD >actual && - echo 4 >expect && - test_cmp expect actual && - git rev-parse --short=10 HEAD~1 >actual && - test_cmp myparent actual - ) -' - -test_expect_success 'reset to specific refs' ' - test_create_repo reset_to_specific_refs && - ( - cd reset_to_specific_refs && - - git commit --allow-empty -m initial && - INITIAL=$(git rev-parse HEAD) && - echo "$INITIAL refs/heads/develop" >expect && - - cat >input <<-INPUT_END && - reset refs/heads/develop - from $INITIAL - - reset refs/heads/master - from 0000000000000000000000000000000000000000 - INPUT_END - - cat input | git filter-repo --force --stdin && - git show-ref >actual && - test_cmp expect actual - ) -' - -setup_handle_funny_characters() { - test -d funny_chars && return - test_create_repo funny_chars && - ( - cd funny_chars && - - git symbolic-ref HEAD refs/heads/españa && - - printf "بتتكلم بالهندي؟\n" >señor && - printf "Αυτά μου φαίνονται αλαμπουρνέζικα.\n" >>señor && - printf "זה סינית בשבילי\n" >>señor && - printf "ちんぷんかんぷん\n" >>señor && - printf "За мене тоа е шпанско село\n" >>señor && - printf "看起来像天书。\n" >>señor && - printf "انگار ژاپنی حرف می زنه\n" >>señor && - printf "Это для меня китайская грамота.\n" >>señor && - printf "To mi je španska vas\n" >>señor && - printf "Konuya Fransız kaldım\n" >>señor && - printf "עס איז די שפּראַך פון גיבבעריש\n" >>señor && - printf "Not even UTF-8:\xe0\x80\x80\x00\n" >>señor && - - cp señor señora && - cp señor señorita && - git add . && - - export GIT_AUTHOR_NAME="Nguyễn Arnfjörð Gábor" && - export GIT_COMMITTER_NAME=$GIT_AUTHOR_NAME && - export GIT_AUTHOR_EMAIL="emails@are.ascii" && - export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL" && - git commit -m "€$£₽₪" && - - git tag -a -m "₪₽£€$" סְפָרַד - ) -} - -test_expect_success 'handle funny characters' ' - setup_handle_funny_characters && - ( - git clone file://"$(pwd)"/funny_chars funny_chars_checks && - cd funny_chars_checks && - - file_sha=$(git rev-parse :0:señor) && - former_head_sha=$(git rev-parse HEAD) && - git filter-repo --replace-refs old-default --to-subdirectory-filter títulos && - - cat <<-EOF >expect && - 100644 $file_sha 0 "t\303\255tulos/se\303\261or" - 100644 $file_sha 0 "t\303\255tulos/se\303\261ora" - 100644 $file_sha 0 "t\303\255tulos/se\303\261orita" - EOF - - git ls-files -s >actual && - test_cmp expect actual && - - commit_sha=$(git rev-parse HEAD) && - tag_sha=$(git rev-parse סְפָרַד) && - cat <<-EOF >expect && - $commit_sha refs/heads/españa - $commit_sha refs/replace/$former_head_sha - $tag_sha refs/tags/סְפָרַד - EOF - - git show-ref >actual && - test_cmp expect actual && - - echo "€$£₽₪" >expect && - git cat-file -p HEAD | tail -n 1 >actual && - - echo "₪₽£€$" >expect && - git cat-file -p סְפָרַד | tail -n 1 >actual - ) -' - -test_expect_success '--state-branch with changing renames' ' - test_create_repo state_branch_renames_export - test_create_repo state_branch_renames && - ( - cd state_branch_renames && - git fast-import --quiet <$DATA/basic-numbers && - git branch -d A && - git branch -d B && - git tag -d v1.0 && - - ORIG=$(git rev-parse master) && - git reset --hard master~1 && - git filter-repo --path-rename ten:zehn \ - --state-branch state_info \ - --target ../state_branch_renames_export && - - cd ../state_branch_renames_export && - git log --format=%s --name-status >actual && - cat <<-EOF >expect && - Merge branch ${SQ}A${SQ} into B - add twenty - - M twenty - add ten - - M zehn - Initial - - A twenty - A zehn - EOF - test_cmp expect actual && - - cd ../state_branch_renames && - - git reset --hard $ORIG && - git filter-repo --path-rename twenty:veinte \ - --state-branch state_info \ - --target ../state_branch_renames_export && - - cd ../state_branch_renames_export && - git log --format=%s --name-status >actual && - cat <<-EOF >expect && - whatever - - A ten - A veinte - Merge branch ${SQ}A${SQ} into B - add twenty - - M twenty - add ten - - M zehn - Initial - - A twenty - A zehn - EOF - test_cmp expect actual - ) -' - -test_expect_success '--state-branch with expanding paths and refs' ' - test_create_repo state_branch_more_paths_export - test_create_repo state_branch_more_paths && - ( - cd state_branch_more_paths && - git fast-import --quiet <$DATA/basic-numbers && - - git reset --hard master~1 && - git filter-repo --path ten --state-branch state_info \ - --target ../state_branch_more_paths_export \ - --refs master && - - cd ../state_branch_more_paths_export && - echo 2 >expect && - git rev-list --count master >actual && - test_cmp expect actual && - test_must_fail git rev-parse master~1:twenty && - test_must_fail git rev-parse master:twenty && - - cd ../state_branch_more_paths && - - git reset --hard v1.0 && - git filter-repo --path ten --path twenty \ - --state-branch state_info \ - --target ../state_branch_more_paths_export && - - cd ../state_branch_more_paths_export && - echo 3 >expect && - git rev-list --count master >actual && - test_cmp expect actual && - test_must_fail git rev-parse master~2:twenty && - git rev-parse master:twenty - ) -' - -test_expect_success FUNNYNAMES 'degenerate merge with non-matching filenames' ' - test_create_repo degenerate_merge_differing_filenames && - ( - cd degenerate_merge_differing_filenames && - - touch "foo \"quote\" bar" && - git add "foo \"quote\" bar" && - git commit -m "Add foo \"quote\" bar" - git branch A && - - git checkout --orphan B && - git reset --hard && - mkdir -p pkg/list && - test_commit pkg/list/whatever && - test_commit unwanted_file && - - git checkout A && - git merge --allow-unrelated-histories --no-commit B && - >pkg/list/wanted && - git add pkg/list/wanted && - git rm -f pkg/list/whatever.t && - git commit && - - git filter-repo --force --path pkg/list && - ! test_path_is_file pkg/list/whatever.t && - git ls-files >actual && - echo pkg/list/wanted >expect && - test_cmp expect actual - ) -' - -test_expect_success 'degenerate merge with typechange' ' - test_create_repo degenerate_merge_with_typechange && - ( - cd degenerate_merge_with_typechange && - - touch irrelevant_file && - git add irrelevant_file && - git commit -m "Irrelevant, unwanted file" - git branch A && - - git checkout --orphan B && - git reset --hard && - echo hello >world && - git add world && - git commit -m "greeting" && - echo goodbye >planet && - git add planet && - git commit -m "farewell" && - - git checkout A && - git merge --allow-unrelated-histories --no-commit B && - rm world && - ln -s planet world && - git add world && - git commit && - - git filter-repo --force --path world && - test_path_is_missing irrelevant_file && - test_path_is_missing planet && - echo world >expect && - git ls-files >actual && - test_cmp expect actual && - - git log --oneline HEAD >input && - test_line_count = 2 input - ) -' - -test_expect_success 'degenerate evil merge' ' - test_create_repo degenerate_evil_merge && - ( - cd degenerate_evil_merge && - - cat $DATA/degenerate-evil-merge | git fast-import --quiet && - git filter-repo --force --subdirectory-filter module-of-interest && - test_path_is_missing module-of-interest && - test_path_is_missing other-module && - test_path_is_missing irrelevant && - test_path_is_file file1 && - test_path_is_file file2 && - test_path_is_file file3 - ) -' - -test_expect_success 'Filtering a blob to make it match previous version' ' - test_create_repo remove_unique_bits_of_blob && - ( - cd remove_unique_bits_of_blob && - - test_write_lines foo baz >metasyntactic_names && - git add metasyntactic_names && - git commit -m init && - - test_write_lines foo bar baz >metasyntactic_names && - git add metasyntactic_names && - git commit -m second && - - git filter-repo --force --blob-callback "blob.data = blob.data.replace(b\"\\nbar\", b\"\")" - - echo 1 >expect && - git rev-list --count HEAD >actual && - test_cmp expect actual - ) -' - -test_expect_success 'tweaking just a tag' ' - test_create_repo tweaking_just_a_tag && - ( - cd tweaking_just_a_tag && - - test_commit foo && - git tag -a -m "Here is a tag" mytag && - - git filter-repo --force --refs mytag ^mytag^{commit} --name-callback "return name.replace(b\"Mitter\", b\"L D\")" && - - git cat-file -p mytag | grep C.O.L.D - ) -' - -test_lazy_prereq IN_FILTER_REPO_CLONE ' - git -C ../../ rev-parse HEAD:git-filter-repo && - grep @@LOCALEDIR@@ ../../../git-filter-repo && - head -n 1 ../../../git-filter-repo | grep "/usr/bin/env python3$" -' - -# Next test depends on git-filter-repo coming from the git-filter-repo -# not having been modified by e.g. normal installation. Skip the test -# if we're in some kind of installation of filter-repo rather than in a -# simple clone of the original repository. -test_expect_success IN_FILTER_REPO_CLONE '--version' ' - git filter-repo --version >actual && - git hash-object ../../git-filter-repo | cut -c 1-12 >expect && - test_cmp expect actual -' - -test_expect_success 'empty author ident' ' - test_create_repo empty_author_ident && - ( - cd empty_author_ident && - - git init && - cat <<-EOF | git fast-import --quiet && - feature done - blob - mark :1 - data 8 - initial - - reset refs/heads/develop - commit refs/heads/develop - mark :2 - author 1535228562 -0700 - committer Full Name 1535228562 -0700 - data 8 - Initial - M 100644 :1 filename - - done - EOF - - git filter-repo --force --path-rename filename:stuff && - - git log --format=%an develop >actual && - echo >expect && - test_cmp expect actual - ) -' - -test_expect_success 'rewrite stash' ' - test_create_repo rewrite_stash && - ( - cd rewrite_stash && - - git init && - test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers && - git add numbers && - git commit -qm initial && - - echo 11 >>numbers && - git stash push -m "add eleven" && - echo foobar >>numbers && - git stash push -m "add foobar" && - - git filter-repo --force --path-rename numbers:values && - - git stash list >output && - test 2 -eq $(cat output | wc -l) - ) -' - -test_expect_success POSIXPERM 'failure to run cleanup' ' - test_create_repo fail_to_cleanup && - ( - cd fail_to_cleanup && - - git init && - test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers && - git add numbers && - git commit -qm initial && - - chmod u-w .git/logs && - test_must_fail git filter-repo --force \ - --path-rename numbers:values 2> ../err && - chmod u+w .git/logs && - grep fatal.*git.reflog.expire.*failed ../err - ) -' - -test_expect_success 'origin refs without origin remote does not die' ' - test_create_repo origin_refs_with_origin_remote && - ( - cd origin_refs_with_origin_remote && - - test_commit numbers && - git update-ref refs/remotes/origin/svnhead master && - - git filter-repo --force --path-rename numbers.t:values.t && - - git show svnhead:values.t >actual && - echo numbers >expect && - test_cmp expect actual - ) -' - test_done diff --git a/t/t9390/date-order b/t/t9394/date-order similarity index 100% rename from t/t9390/date-order rename to t/t9394/date-order