Merge branch 'pw/git-p4'
authorJunio C Hamano <gitster@pobox.com>
Mon, 27 Jan 2014 18:45:41 +0000 (10:45 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 27 Jan 2014 18:45:41 +0000 (10:45 -0800)
Various "git p4" updates.

* pw/git-p4:
git p4 doc: use two-line style for options with multiple spellings
git p4 test: examine behavior with locked (+l) files
git p4: fix an error message when "p4 where" fails
git p4: handle files with wildcards when doing RCS scrubbing
git p4 test: do not pollute /tmp
git p4 test: run as user "author"
git p4 test: is_cli_file_writeable succeeds
git p4 test: explicitly check p4 wildcard delete
git p4: work around p4 bug that causes empty symlinks
git p4 test: ensure p4 symlink parsing works
git p4 test: wildcards are supported

Documentation/git-p4.txt
git-p4.py
t/lib-git-p4.sh
t/t9802-git-p4-filetype.sh
t/t9805-git-p4-skip-submit-edit.sh
t/t9807-git-p4-submit.sh
t/t9809-git-p4-client-view.sh
t/t9812-git-p4-wildcards.sh
t/t9813-git-p4-preserve-users.sh
t/t9816-git-p4-locked.sh [new file with mode: 0755]
index 8cba16d67f29ec48738b00a607add4a20bc7d4b2..6ab5f9497ab353e38c15708443527e1d047d39e8 100644 (file)
@@ -168,7 +168,8 @@ All commands except clone accept these options.
 --git-dir <dir>::
        Set the 'GIT_DIR' environment variable.  See linkgit:git[1].
 
---verbose, -v::
+-v::
+--verbose::
        Provide more progress information.
 
 Sync options
@@ -279,7 +280,8 @@ These options can be used to modify 'git p4 submit' behavior.
        Export tags from Git as p4 labels. Tags found in Git are applied
        to the perforce working directory.
 
---dry-run, -n::
+-n::
+--dry-run::
        Show just what commits would be submitted to p4; do not change
        state in Git or p4.
 
index 5ea8bb8fc27520cb6874431e827c00821b848b33..cdfa2df5d8e0add0363f3e246362b437b72c4d58 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -310,8 +310,8 @@ def split_p4_type(p4type):
 #
 # return the raw p4 type of a file (text, text+ko, etc)
 #
-def p4_type(file):
-    results = p4CmdList(["fstat", "-T", "headType", file])
+def p4_type(f):
+    results = p4CmdList(["fstat", "-T", "headType", wildcard_encode(f)])
     return results[0]['headType']
 
 #
@@ -1220,7 +1220,7 @@ def edit_template(self, template_file):
             editor = os.environ.get("P4EDITOR")
         else:
             editor = read_pipe("git var GIT_EDITOR").strip()
-        system(editor + " " + template_file)
+        system([editor, template_file])
 
         # If the file was not saved, prompt to see if this patch should
         # be skipped.  But skip this verification step if configured so.
@@ -1871,7 +1871,7 @@ def update_client_spec_path_cache(self, files):
                 # assume error is "... file(s) not in client view"
                 continue
             if "clientFile" not in res:
-                die("No clientFile from 'p4 where %s'" % depot_path)
+                die("No clientFile in 'p4 where' output")
             if "unmap" in res:
                 # it will list all of them, but only one not unmap-ped
                 continue
@@ -2075,7 +2075,14 @@ def streamOneP4File(self, file, contents):
             # p4 print on a symlink sometimes contains "target\n";
             # if it does, remove the newline
             data = ''.join(contents)
-            if data[-1] == '\n':
+            if not data:
+                # Some version of p4 allowed creating a symlink that pointed
+                # to nothing.  This causes p4 errors when checking out such
+                # a change, and errors here too.  Work around it by ignoring
+                # the bad symlink; hopefully a future change fixes it.
+                print "\nIgnoring empty symlink in %s" % file['depotFile']
+                return
+            elif data[-1] == '\n':
                 contents = [data[:-1]]
             else:
                 contents = [data]
index ccd918e79ef4fa7962984c5ed4890ec07e92f03a..5aa8adcf9c8a53b8a643a37b9474886448f6a45b 100644 (file)
@@ -47,15 +47,22 @@ P4DPORT=$((10669 + ($testid - $git_p4_test_start)))
 
 P4PORT=localhost:$P4DPORT
 P4CLIENT=client
-P4EDITOR=:
+P4USER=author
+P4EDITOR=true
 unset P4CHARSET
-export P4PORT P4CLIENT P4EDITOR P4CHARSET
+export P4PORT P4CLIENT P4USER P4EDITOR P4CHARSET
 
 db="$TRASH_DIRECTORY/db"
 cli="$TRASH_DIRECTORY/cli"
 git="$TRASH_DIRECTORY/git"
 pidfile="$TRASH_DIRECTORY/p4d.pid"
 
+# git p4 submit generates a temp file, which will
+# not get cleaned up if the submission fails.  Don't
+# clutter up /tmp on the test machine.
+TMPDIR="$TRASH_DIRECTORY"
+export TMPDIR
+
 start_p4d() {
        mkdir -p "$db" "$cli" "$git" &&
        rm -f "$pidfile" &&
@@ -96,12 +103,24 @@ start_p4d() {
                return 1
        fi
 
+       # build a p4 user so author@example.com has an entry
+       p4_add_user author
+
        # build a client
        client_view "//depot/... //client/..." &&
 
        return 0
 }
 
+p4_add_user() {
+       name=$1 &&
+       p4 user -f -i <<-EOF
+       User: $name
+       Email: $name@example.com
+       FullName: Dr. $name
+       EOF
+}
+
 kill_p4d() {
        pid=$(cat "$pidfile")
        # it had better exist for the first kill
index a82744bab072114675dea66fa94f21f661aa8dbd..66d3fc91a739a7f04bb73694a2f5d75034045d10 100755 (executable)
@@ -250,6 +250,89 @@ test_expect_success 'ignore apple' '
        )
 '
 
+test_expect_success SYMLINKS 'create p4 symlink' '
+       cd "$cli" &&
+       ln -s symlink-target symlink &&
+       p4 add symlink &&
+       p4 submit -d "add symlink"
+'
+
+test_expect_success SYMLINKS 'ensure p4 symlink parsed correctly' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot@all &&
+       (
+               cd "$git" &&
+               test -L symlink &&
+               test $(readlink symlink) = symlink-target
+       )
+'
+
+test_expect_success SYMLINKS 'empty symlink target' '
+       (
+               # first create the file as a file
+               cd "$cli" &&
+               >empty-symlink &&
+               p4 add empty-symlink &&
+               p4 submit -d "add empty-symlink as a file"
+       ) &&
+       (
+               # now change it to be a symlink to "target1"
+               cd "$cli" &&
+               p4 edit empty-symlink &&
+               p4 reopen -t symlink empty-symlink &&
+               rm empty-symlink &&
+               ln -s target1 empty-symlink &&
+               p4 add empty-symlink &&
+               p4 submit -d "make empty-symlink point to target1"
+       ) &&
+       (
+               # Hack the p4 depot to make the symlink point to nothing;
+               # this should not happen in reality, but shows up
+               # in p4 repos in the wild.
+               #
+               # The sed expression changes this:
+               #     @@
+               #     text
+               #     @target1
+               #     @
+               # to this:
+               #     @@
+               #     text
+               #     @@
+               #
+               cd "$db/depot" &&
+               sed "/@target1/{; s/target1/@/; n; d; }" \
+                   empty-symlink,v >empty-symlink,v.tmp &&
+               mv empty-symlink,v.tmp empty-symlink,v
+       ) &&
+       (
+               # Make sure symlink really is empty.  Asking
+               # p4 to sync here will make it generate errors.
+               cd "$cli" &&
+               p4 print -q //depot/empty-symlink#2 >out &&
+               test ! -s out
+       ) &&
+       test_when_finished cleanup_git &&
+
+       # make sure git p4 handles it without error
+       git p4 clone --dest="$git" //depot@all &&
+
+       # fix the symlink, make it point to "target2"
+       (
+               cd "$cli" &&
+               p4 open empty-symlink &&
+               rm empty-symlink &&
+               ln -s target2 empty-symlink &&
+               p4 submit -d "make empty-symlink point to target2"
+       ) &&
+       cleanup_git &&
+       git p4 clone --dest="$git" //depot@all &&
+       (
+               cd "$git" &&
+               test $(readlink empty-symlink) = target2
+       )
+'
+
 test_expect_success 'kill p4d' '
        kill_p4d
 '
index ff2cc79701cce5ff570ff3c11d1ad0b60459d9ef..89311886db168e6613fa98d9c7a340d460435585 100755 (executable)
@@ -17,7 +17,7 @@ test_expect_success 'init depot' '
        )
 '
 
-# this works because EDITOR is set to :
+# this works because P4EDITOR is set to true
 test_expect_success 'no config, unedited, say yes' '
        git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
@@ -90,7 +90,9 @@ test_expect_success 'no config, edited' '
                cd "$git" &&
                echo line >>file1 &&
                git commit -a -m "change 5" &&
-               P4EDITOR="" EDITOR="\"$TRASH_DIRECTORY/ed.sh\"" git p4 submit &&
+               P4EDITOR="$TRASH_DIRECTORY/ed.sh" &&
+               export P4EDITOR &&
+               git p4 submit &&
                p4 changes //depot/... >wc &&
                test_line_count = 5 wc
        )
index 1fb7bc7cf98bdbdf89491c687e6347cda673d720..4caf36e0066313f5402f61645c4330093ba23fcf 100755 (executable)
@@ -17,7 +17,7 @@ test_expect_success 'init depot' '
        )
 '
 
-test_expect_failure 'is_cli_file_writeable function' '
+test_expect_success 'is_cli_file_writeable function' '
        (
                cd "$cli" &&
                echo a >a &&
index 77f63492d90e61500e6665ba447fbbad7a8c7b3b..23a827fa77d0a5d527cef29a15a205ee14d68e27 100755 (executable)
@@ -76,28 +76,28 @@ test_expect_success 'init depot' '
 '
 
 # double % for printf
-test_expect_success 'unsupported view wildcard %%n' '
+test_expect_success 'view wildcard %%n' '
        client_view "//depot/%%%%1/sub/... //client/sub/%%%%1/..." &&
        test_when_finished cleanup_git &&
-       test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
+       git p4 clone --use-client-spec --dest="$git" //depot
 '
 
-test_expect_success 'unsupported view wildcard *' '
+test_expect_success 'view wildcard *' '
        client_view "//depot/*/bar/... //client/*/bar/..." &&
        test_when_finished cleanup_git &&
-       test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
+       git p4 clone --use-client-spec --dest="$git" //depot
 '
 
-test_expect_success 'wildcard ... only supported at end of spec 1' '
+test_expect_success 'wildcard ... in the middle' '
        client_view "//depot/.../file11 //client/.../file11" &&
        test_when_finished cleanup_git &&
-       test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
+       git p4 clone --use-client-spec --dest="$git" //depot
 '
 
-test_expect_success 'wildcard ... only supported at end of spec 2' '
+test_expect_success 'wildcard ... in the middle and at the end' '
        client_view "//depot/.../a/... //client/.../a/..." &&
        test_when_finished cleanup_git &&
-       test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
+       git p4 clone --use-client-spec --dest="$git" //depot
 '
 
 test_expect_success 'basic map' '
index 67633257f3fccb2408d678759bce87f7a58acd0a..c7472cbf54584c7610094677693114d41b422afe 100755 (executable)
@@ -161,6 +161,56 @@ test_expect_success 'wildcard files submit back to p4, delete' '
        )
 '
 
+test_expect_success 'p4 deleted a wildcard file' '
+       (
+               cd "$cli" &&
+               echo "wild delete test" >wild@delete &&
+               p4 add -f wild@delete &&
+               p4 submit -d "add wild@delete"
+       ) &&
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               test_path_is_file wild@delete
+       ) &&
+       (
+               cd "$cli" &&
+               # must use its encoded name
+               p4 delete wild%40delete &&
+               p4 submit -d "delete wild@delete"
+       ) &&
+       (
+               cd "$git" &&
+               git p4 sync &&
+               git merge --ff-only p4/master &&
+               test_path_is_missing wild@delete
+       )
+'
+
+test_expect_success 'wildcard files requiring keyword scrub' '
+       (
+               cd "$cli" &&
+               cat <<-\EOF >scrub@wild &&
+               $Id$
+               line2
+               EOF
+               p4 add -t text+k -f scrub@wild &&
+               p4 submit -d "scrub at wild"
+       ) &&
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.attemptRCSCleanup true &&
+               sed "s/^line2/line2 edit/" <scrub@wild >scrub@wild.tmp &&
+               mv -f scrub@wild.tmp scrub@wild &&
+               git commit -m "scrub at wild line2 edit" scrub@wild &&
+               git p4 submit
+       )
+'
+
 test_expect_success 'kill p4d' '
        kill_p4d
 '
index f2e85e518b650d9760c74533325e5a2d75154ebb..166b840bfae01d65e02d9ac7f7187142829d4f5f 100755 (executable)
@@ -19,16 +19,6 @@ test_expect_success 'create files' '
        )
 '
 
-p4_add_user() {
-       name=$1 fullname=$2 &&
-       p4 user -f -i <<-EOF &&
-       User: $name
-       Email: $name@localhost
-       FullName: $fullname
-       EOF
-       p4 passwd -P secret $name
-}
-
 p4_grant_admin() {
        name=$1 &&
        {
@@ -51,8 +41,8 @@ make_change_by_user() {
 
 # Test username support, submitting as user 'alice'
 test_expect_success 'preserve users' '
-       p4_add_user alice Alice &&
-       p4_add_user bob Bob &&
+       p4_add_user alice &&
+       p4_add_user bob &&
        p4_grant_admin alice &&
        git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
@@ -60,8 +50,8 @@ test_expect_success 'preserve users' '
                cd "$git" &&
                echo "username: a change by alice" >>file1 &&
                echo "username: a change by bob" >>file2 &&
-               git commit --author "Alice <alice@localhost>" -m "a change by alice" file1 &&
-               git commit --author "Bob <bob@localhost>" -m "a change by bob" file2 &&
+               git commit --author "Alice <alice@example.com>" -m "a change by alice" file1 &&
+               git commit --author "Bob <bob@example.com>" -m "a change by bob" file2 &&
                git config git-p4.skipSubmitEditCheck true &&
                P4EDITOR=touch P4USER=alice P4PASSWD=secret git p4 commit --preserve-user &&
                p4_check_commit_author file1 alice &&
@@ -78,7 +68,7 @@ test_expect_success 'refuse to preserve users without perms' '
                cd "$git" &&
                git config git-p4.skipSubmitEditCheck true &&
                echo "username-noperms: a change by alice" >>file1 &&
-               git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 &&
+               git commit --author "Alice <alice@example.com>" -m "perms: a change by alice" file1 &&
                P4EDITOR=touch P4USER=bob P4PASSWD=secret &&
                export P4EDITOR P4USER P4PASSWD &&
                test_must_fail git p4 commit --preserve-user &&
@@ -94,9 +84,9 @@ test_expect_success 'preserve user where author is unknown to p4' '
                cd "$git" &&
                git config git-p4.skipSubmitEditCheck true &&
                echo "username-bob: a change by bob" >>file1 &&
-               git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 &&
+               git commit --author "Bob <bob@example.com>" -m "preserve: a change by bob" file1 &&
                echo "username-unknown: a change by charlie" >>file1 &&
-               git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 &&
+               git commit --author "Charlie <charlie@example.com>" -m "preserve: a change by charlie" file1 &&
                P4EDITOR=touch P4USER=alice P4PASSWD=secret &&
                export P4EDITOR P4USER P4PASSWD &&
                test_must_fail git p4 commit --preserve-user &&
@@ -121,24 +111,24 @@ test_expect_success 'not preserving user with mixed authorship' '
        (
                cd "$git" &&
                git config git-p4.skipSubmitEditCheck true &&
-               p4_add_user derek Derek &&
+               p4_add_user derek &&
 
-               make_change_by_user usernamefile3 Derek derek@localhost &&
+               make_change_by_user usernamefile3 Derek derek@example.com &&
                P4EDITOR=cat P4USER=alice P4PASSWD=secret &&
                export P4EDITOR P4USER P4PASSWD &&
                git p4 commit |\
-               grep "git author derek@localhost does not match" &&
+               grep "git author derek@example.com does not match" &&
 
-               make_change_by_user usernamefile3 Charlie charlie@localhost &&
+               make_change_by_user usernamefile3 Charlie charlie@example.com &&
                git p4 commit |\
-               grep "git author charlie@localhost does not match" &&
+               grep "git author charlie@example.com does not match" &&
 
-               make_change_by_user usernamefile3 alice alice@localhost &&
+               make_change_by_user usernamefile3 alice alice@example.com &&
                git p4 commit |\
                test_must_fail grep "git author.*does not match" &&
 
                git config git-p4.skipUserNameCheck true &&
-               make_change_by_user usernamefile3 Charlie charlie@localhost &&
+               make_change_by_user usernamefile3 Charlie charlie@example.com &&
                git p4 commit |\
                test_must_fail grep "git author.*does not match" &&
 
diff --git a/t/t9816-git-p4-locked.sh b/t/t9816-git-p4-locked.sh
new file mode 100755 (executable)
index 0000000..e71e543
--- /dev/null
@@ -0,0 +1,145 @@
+#!/bin/sh
+
+test_description='git p4 locked file behavior'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+# See
+# http://www.perforce.com/perforce/doc.current/manuals/p4sag/03_superuser.html#1088563
+# for suggestions on how to configure "sitewide pessimistic locking"
+# where only one person can have a file open for edit at a time.
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               echo "TypeMap: +l //depot/..." | p4 typemap -i &&
+               echo file1 >file1 &&
+               p4 add file1 &&
+               p4 submit -d "add file1"
+       )
+'
+
+test_expect_success 'edit with lock not taken' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo line2 >>file1 &&
+               git add file1 &&
+               git commit -m "line2 in file1" &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit
+       )
+'
+
+test_expect_failure 'add with lock not taken' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo line1 >>add-lock-not-taken &&
+               git add file2 &&
+               git commit -m "add add-lock-not-taken" &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit --verbose
+       )
+'
+
+lock_in_another_client() {
+       # build a different client
+       cli2="$TRASH_DIRECTORY/cli2" &&
+       mkdir -p "$cli2" &&
+       test_when_finished "p4 client -f -d client2 && rm -rf \"$cli2\"" &&
+       (
+               cd "$cli2" &&
+               P4CLIENT=client2 &&
+               cli="$cli2" &&
+               client_view "//depot/... //client2/..." &&
+               p4 sync &&
+               p4 open file1
+       )
+}
+
+test_expect_failure 'edit with lock taken' '
+       lock_in_another_client &&
+       test_when_finished cleanup_git &&
+       test_when_finished "cd \"$cli\" && p4 sync -f file1" &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo line3 >>file1 &&
+               git add file1 &&
+               git commit -m "line3 in file1" &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit --verbose
+       )
+'
+
+test_expect_failure 'delete with lock taken' '
+       lock_in_another_client &&
+       test_when_finished cleanup_git &&
+       test_when_finished "cd \"$cli\" && p4 sync -f file1" &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git rm file1 &&
+               git commit -m "delete file1" &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit --verbose
+       )
+'
+
+test_expect_failure 'chmod with lock taken' '
+       lock_in_another_client &&
+       test_when_finished cleanup_git &&
+       test_when_finished "cd \"$cli\" && p4 sync -f file1" &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               chmod +x file1 &&
+               git add file1 &&
+               git commit -m "chmod +x file1" &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit --verbose
+       )
+'
+
+test_expect_failure 'copy with lock taken' '
+       lock_in_another_client &&
+       test_when_finished cleanup_git &&
+       test_when_finished "cd \"$cli\" && p4 revert file2 && rm -f file2" &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               cp file1 file2 &&
+               git add file2 &&
+               git commit -m "cp file1 to file2" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.detectCopies true &&
+               git p4 submit --verbose
+       )
+'
+
+test_expect_failure 'move with lock taken' '
+       lock_in_another_client &&
+       test_when_finished cleanup_git &&
+       test_when_finished "cd \"$cli\" && p4 sync file1 && rm -f file2" &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git mv file1 file2 &&
+               git commit -m "mv file1 to file2" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.detectRenames true &&
+               git p4 submit --verbose
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done