{"thread":{"id":"29280","subject":"[PATCH 0/6] git-p4: view spec improvements","startedAt":"2012-01-02T23:05:48Z","lastAt":"2012-01-02T23:05:54Z","messageCount":7,"participants":["Pete Wyckoff"],"isPatch":true,"patchVersion":1,"patchTotal":6},"messages":[{"id":"181842","messageId":"1325545554-16540-1-git-send-email-pw@padd.com","threadId":"29280","inReplyTo":null,"subject":"[PATCH 0/6] git-p4: view spec improvements","fromName":"Pete Wyckoff","fromEmail":"pw@padd.com","sentAt":"2012-01-02T23:05:48Z","receivedAt":"2012-01-02T23:05:48Z","isPatch":true,"sender":{"key":"pw@padd.com","avatar":null},"body":"Gary found and fixed a few bothersome problems with how\ngit-p4 parses the View lines in client specifications.\nI got the itch to fix other issues, and ended up writing\na battery of test cases and redoing the view spec code\nentirely.\n\nPatch summary:\n\n    1.  Add 22 test cases for client view spec handling,\n\tmost of which are expected to fail.  Test review\n\twelcome.\n\n    2-4.  Fix issues with unsupported wildcards, incorrect\n\t  sorting, and add support for single-file maps.\n\n    5.  Rewrite view spec code, fixing the rest of the tests.\n        This is new python that could use some review.\n\n    6.  Update docs.  This depends on pw/p4-docs-and-tests that\n\tis in next.\n\nGary Gibbons (3):\n  git-p4: fix test for unsupported P4 Client Views\n  git-p4: sort client views by reverse View number\n  git-p4: support single file p4 client view maps\n\nPete Wyckoff (3):\n  git-p4: test client view handling\n  git-p4: rewrite view handling\n  git-p4: view spec documentation\n\n Documentation/git-p4.txt      |   40 ++++--\n contrib/fast-import/git-p4    |  316 ++++++++++++++++++++++++++++++++---------\n t/t9809-git-p4-client-view.sh |  290 +++++++++++++++++++++++++++++++++++++\n 3 files changed, 564 insertions(+), 82 deletions(-)\n create mode 100755 t/t9809-git-p4-client-view.sh\n\n-- \n1.7.8.1.407.gd70cb\n"},{"id":"181843","messageId":"1325545554-16540-2-git-send-email-pw@padd.com","threadId":"29280","inReplyTo":"1325545554-16540-1-git-send-email-pw@padd.com","subject":"[PATCH 1/6] git-p4: test client view handling","fromName":"Pete Wyckoff","fromEmail":"pw@padd.com","sentAt":"2012-01-02T23:05:49Z","receivedAt":"2012-01-02T23:05:49Z","isPatch":true,"sender":{"key":"pw@padd.com","avatar":null},"body":"Test many aspects of processing p4 client views with the\ngit-p4 option --use-client-spec.  16 out of 22 tests are\ncurrently broken.\n\nSigned-off-by: Pete Wyckoff <pw@padd.com>\n---\n t/t9809-git-p4-client-view.sh |  290 +++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 290 insertions(+), 0 deletions(-)\n create mode 100755 t/t9809-git-p4-client-view.sh\n\ndiff --git a/t/t9809-git-p4-client-view.sh b/t/t9809-git-p4-client-view.sh\nnew file mode 100755\nindex 0000000..4259fb3\n--- /dev/null\n+++ b/t/t9809-git-p4-client-view.sh\n@@ -0,0 +1,290 @@\n+#!/bin/sh\n+\n+test_description='git-p4 client view'\n+\n+. ./lib-git-p4.sh\n+\n+test_expect_success 'start p4d' '\n+\tstart_p4d\n+'\n+\n+#\n+# Construct a client with this list of View lines\n+#\n+client_view() {\n+\t(\n+\t\tcat <<-EOF &&\n+\t\tClient: client\n+\t\tDescription: client\n+\t\tRoot: $cli\n+\t\tView:\n+\t\tEOF\n+\t\tfor arg ; do\n+\t\t\tprintf \"\\t$arg\\n\"\n+\t\tdone\n+\t) | p4 client -i\n+}\n+\n+#\n+# Verify these files exist, exactly.  Caller creates\n+# a list of files in file \"files\".\n+#\n+check_files_exist() {\n+\tok=0 &&\n+\tnum=${#@} &&\n+\tfor arg ; do\n+\t\ttest_path_is_file \"$arg\" &&\n+\t\tok=$(($ok + 1))\n+\tdone &&\n+\ttest $ok -eq $num &&\n+\ttest_line_count = $num files\n+}\n+\n+#\n+# Sync up the p4 client, make sure the given files (and only\n+# those) exist.\n+#\n+client_verify() {\n+\t(\n+\t\tcd \"$cli\" &&\n+\t\tp4 sync &&\n+\t\tfind . -type f ! -name files >files &&\n+\t\tcheck_files_exist \"$@\"\n+\t)\n+}\n+\n+#\n+# Make sure the named files, exactly, exist.\n+#\n+git_verify() {\n+\t(\n+\t\tcd \"$git\" &&\n+\t\tgit ls-files >files &&\n+\t\tcheck_files_exist \"$@\"\n+\t)\n+}\n+\n+# //depot\n+#   - dir1\n+#     - file11\n+#     - file12\n+#   - dir2\n+#     - file21\n+#     - file22\n+test_expect_success 'init depot' '\n+\t(\n+\t\tcd \"$cli\" &&\n+\t\tfor d in 1 2 ; do\n+\t\t\tmkdir -p dir$d &&\n+\t\t\tfor f in 1 2 ; do\n+\t\t\t\techo dir$d/file$d$f >dir$d/file$d$f &&\n+\t\t\t\tp4 add dir$d/file$d$f &&\n+\t\t\t\tp4 submit -d \"dir$d/file$d$f\"\n+\t\t\tdone\n+\t\tdone &&\n+\t\tfind . -type f ! -name files >files &&\n+\t\tcheck_files_exist dir1/file11 dir1/file12 \\\n+\t\t\t\t  dir2/file21 dir2/file22\n+\t)\n+'\n+\n+# double % for printf\n+test_expect_failure 'unsupported view wildcard %%n' '\n+\tclient_view \"//depot/%%%%1/sub/... //client/sub/%%%%1/...\" &&\n+\ttest_when_finished cleanup_git &&\n+\ttest_must_fail \"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot\n+'\n+\n+test_expect_failure 'unsupported view wildcard *' '\n+\tclient_view \"//depot/*/bar/... //client/*/bar/...\" &&\n+\ttest_when_finished cleanup_git &&\n+\ttest_must_fail \"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot\n+'\n+\n+test_expect_success 'wildcard ... only supported at end of spec' '\n+\tclient_view \"//depot/.../file11 //client/.../file11\" &&\n+\ttest_when_finished cleanup_git &&\n+\ttest_must_fail \"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot\n+'\n+\n+test_expect_failure 'basic map' '\n+\tclient_view \"//depot/dir1/... //client/cli1/...\" &&\n+\tfiles=\"cli1/file11 cli1/file12\" &&\n+\tclient_verify $files &&\n+\ttest_when_finished cleanup_git &&\n+\t\"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot &&\n+\tgit_verify $files\n+'\n+\n+test_expect_failure 'client view with no mappings' '\n+\tclient_view &&\n+\tclient_verify &&\n+\ttest_when_finished cleanup_git &&\n+\t\"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot &&\n+\tgit_verify\n+'\n+\n+test_expect_failure 'single file map' '\n+\tclient_view \"//depot/dir1/file11 //client/file11\" &&\n+\tfiles=\"file11\" &&\n+\tclient_verify $files &&\n+\ttest_when_finished cleanup_git &&\n+\t\"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot &&\n+\tgit_verify $files\n+'\n+\n+test_expect_failure 'later mapping takes precedence (entire repo)' '\n+\tclient_view \"//depot/dir1/... //client/cli1/...\" \\\n+\t\t    \"//depot/... //client/cli2/...\" &&\n+\tfiles=\"cli2/dir1/file11 cli2/dir1/file12\n+\t       cli2/dir2/file21 cli2/dir2/file22\" &&\n+\tclient_verify $files &&\n+\ttest_when_finished cleanup_git &&\n+\t\"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot &&\n+\tgit_verify $files\n+'\n+\n+test_expect_failure 'later mapping takes precedence (partial repo)' '\n+\tclient_view \"//depot/dir1/... //client/...\" \\\n+\t\t    \"//depot/dir2/... //client/...\" &&\n+\tfiles=\"file21 file22\" &&\n+\tclient_verify $files &&\n+\ttest_when_finished cleanup_git &&\n+\t\"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot &&\n+\tgit_verify $files\n+'\n+\n+# Reading the view backwards,\n+#   dir2 goes to cli12\n+#   dir1 cannot go to cli12 since it was filled by dir2\n+#   dir1 also does not go to cli3, since the second rule\n+#     noticed that it matched, but was already filled\n+test_expect_failure 'depot path matching rejected client path' '\n+\tclient_view \"//depot/dir1/... //client/cli3/...\" \\\n+\t\t    \"//depot/dir1/... //client/cli12/...\" \\\n+\t\t    \"//depot/dir2/... //client/cli12/...\" &&\n+\tfiles=\"cli12/file21 cli12/file22\" &&\n+\tclient_verify $files &&\n+\ttest_when_finished cleanup_git &&\n+\t\"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot &&\n+\tgit_verify $files\n+'\n+\n+# since both have the same //client/..., the exclusion\n+# rule keeps everything out\n+test_expect_failure 'exclusion wildcard, client rhs same (odd)' '\n+\tclient_view \"//depot/... //client/...\" \\\n+\t\t    \"-//depot/dir2/... //client/...\" &&\n+\tclient_verify &&\n+\ttest_when_finished cleanup_git &&\n+\t\"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot &&\n+\tgit_verify\n+'\n+\n+test_expect_success 'exclusion wildcard, client rhs different (normal)' '\n+\tclient_view \"//depot/... //client/...\" \\\n+\t\t    \"-//depot/dir2/... //client/dir2/...\" &&\n+\tfiles=\"dir1/file11 dir1/file12\" &&\n+\tclient_verify $files &&\n+\ttest_when_finished cleanup_git &&\n+\t\"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot &&\n+\tgit_verify $files\n+'\n+\n+test_expect_failure 'exclusion single file' '\n+\tclient_view \"//depot/... //client/...\" \\\n+\t\t    \"-//depot/dir2/file22 //client/file22\" &&\n+\tfiles=\"dir1/file11 dir1/file12 dir2/file21\" &&\n+\tclient_verify $files &&\n+\ttest_when_finished cleanup_git &&\n+\t\"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot &&\n+\tgit_verify $files\n+'\n+\n+test_expect_failure 'overlay wildcard' '\n+\tclient_view \"//depot/dir1/... //client/cli/...\" \\\n+\t\t    \"+//depot/dir2/... //client/cli/...\\n\" &&\n+\tfiles=\"cli/file11 cli/file12 cli/file21 cli/file22\" &&\n+\tclient_verify $files &&\n+\ttest_when_finished cleanup_git &&\n+\t\"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot &&\n+\tgit_verify $files\n+'\n+\n+test_expect_failure 'overlay single file' '\n+\tclient_view \"//depot/dir1/... //client/cli/...\" \\\n+\t\t    \"+//depot/dir2/file21 //client/cli/file21\" &&\n+\tfiles=\"cli/file11 cli/file12 cli/file21\" &&\n+\tclient_verify $files &&\n+\ttest_when_finished cleanup_git &&\n+\t\"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot &&\n+\tgit_verify $files\n+'\n+\n+test_expect_failure 'exclusion with later inclusion' '\n+\tclient_view \"//depot/... //client/...\" \\\n+\t\t    \"-//depot/dir2/... //client/dir2/...\" \\\n+\t\t    \"//depot/dir2/... //client/dir2incl/...\" &&\n+\tfiles=\"dir1/file11 dir1/file12 dir2incl/file21 dir2incl/file22\" &&\n+\tclient_verify $files &&\n+\ttest_when_finished cleanup_git &&\n+\t\"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot &&\n+\tgit_verify $files\n+'\n+\n+test_expect_failure 'quotes on rhs only' '\n+\tclient_view \"//depot/dir1/... \\\"//client/cdir 1/...\\\"\" &&\n+\tclient_verify \"cdir 1/file11\" \"cdir 1/file12\" &&\n+\ttest_when_finished cleanup_git &&\n+\t\"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot &&\n+\tgit_verify \"cdir 1/file11\" \"cdir 1/file12\"\n+'\n+\n+#\n+# Rename directories to test quoting in depot-side mappings\n+# //depot\n+#    - \"dir 1\"\n+#       - file11\n+#       - file12\n+#    - \"dir 2\"\n+#       - file21\n+#       - file22\n+#\n+test_expect_success 'rename files to introduce spaces' '\n+\tclient_view \"//depot/... //client/...\" &&\n+\tclient_verify dir1/file11 dir1/file12 \\\n+\t\t      dir2/file21 dir2/file22 &&\n+\t(\n+\t\tcd \"$cli\" &&\n+\t\tp4 open dir1/... &&\n+\t\tp4 move dir1/... \"dir 1\"/... &&\n+\t\tp4 open dir2/... &&\n+\t\tp4 move dir2/... \"dir 2\"/... &&\n+\t\tp4 submit -d \"rename with spaces\"\n+\t) &&\n+\tclient_verify \"dir 1/file11\" \"dir 1/file12\" \\\n+\t\t      \"dir 2/file21\" \"dir 2/file22\"\n+'\n+\n+test_expect_failure 'quotes on lhs only' '\n+\tclient_view \"\\\"//depot/dir 1/...\\\" //client/cdir1/...\" &&\n+\tfiles=\"cdir1/file11 cdir1/file12\" &&\n+\tclient_verify $files &&\n+\ttest_when_finished cleanup_git &&\n+\t\"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot &&\n+\tclient_verify $files\n+'\n+\n+test_expect_failure 'quotes on both sides' '\n+\tclient_view \"\\\"//depot/dir 1/...\\\" \\\"//client/cdir 1/...\\\"\" &&\n+\tclient_verify \"cdir 1/file11\" \"cdir 1/file12\" &&\n+\ttest_when_finished cleanup_git &&\n+\t\"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot &&\n+\tgit_verify \"cdir 1/file11\" \"cdir 1/file12\"\n+'\n+\n+test_expect_success 'kill p4d' '\n+\tkill_p4d\n+'\n+\n+test_done\n-- \n1.7.8.1.407.gd70cb\n"},{"id":"181844","messageId":"1325545554-16540-3-git-send-email-pw@padd.com","threadId":"29280","inReplyTo":"1325545554-16540-1-git-send-email-pw@padd.com","subject":"[PATCH 2/6] git-p4: fix test for unsupported P4 Client Views","fromName":"Pete Wyckoff","fromEmail":"pw@padd.com","sentAt":"2012-01-02T23:05:50Z","receivedAt":"2012-01-02T23:05:50Z","isPatch":true,"sender":{"key":"pw@padd.com","avatar":null},"body":"From: Gary Gibbons <ggibbons@perforce.com>\n\nChange re method in test for unsupported Client View types\n(containing %% or *) anywhere in the string rather than\nat the begining.\n\n[pw: two tests now succeed]\n\nSigned-off-by: Gary Gibbons <ggibbons@perforce.com>\nSigned-off-by: Pete Wyckoff <pw@padd.com>\n---\n contrib/fast-import/git-p4    |    5 ++++-\n t/t9809-git-p4-client-view.sh |    4 ++--\n 2 files changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4\nindex d3c3ad8..c144c89 100755\n--- a/contrib/fast-import/git-p4\n+++ b/contrib/fast-import/git-p4\n@@ -1889,9 +1889,12 @@ class P4Sync(Command, P4UserMap):\n \n                     # p4 has these %%1 to %%9 arguments in specs to\n                     # reorder paths; which we can't handle (yet :)\n-                    if re.match('%%\\d', v) != None:\n+                    if re.search('%%\\d', v) != None:\n                         print \"Sorry, can't handle %%n arguments in client specs\"\n                         sys.exit(1)\n+                    if re.search('\\*', v) != None:\n+                        print \"Sorry, can't handle * mappings in client specs\"\n+                        sys.exit(1)\n \n                     if v.startswith('\"'):\n                         start = 1\ndiff --git a/t/t9809-git-p4-client-view.sh b/t/t9809-git-p4-client-view.sh\nindex 4259fb3..cbf2213 100755\n--- a/t/t9809-git-p4-client-view.sh\n+++ b/t/t9809-git-p4-client-view.sh\n@@ -89,13 +89,13 @@ test_expect_success 'init depot' '\n '\n \n # double % for printf\n-test_expect_failure 'unsupported view wildcard %%n' '\n+test_expect_success 'unsupported view wildcard %%n' '\n \tclient_view \"//depot/%%%%1/sub/... //client/sub/%%%%1/...\" &&\n \ttest_when_finished cleanup_git &&\n \ttest_must_fail \"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot\n '\n \n-test_expect_failure 'unsupported view wildcard *' '\n+test_expect_success 'unsupported view wildcard *' '\n \tclient_view \"//depot/*/bar/... //client/*/bar/...\" &&\n \ttest_when_finished cleanup_git &&\n \ttest_must_fail \"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot\n-- \n1.7.8.1.407.gd70cb\n"},{"id":"181845","messageId":"1325545554-16540-4-git-send-email-pw@padd.com","threadId":"29280","inReplyTo":"1325545554-16540-1-git-send-email-pw@padd.com","subject":"[PATCH 3/6] git-p4: sort client views by reverse View number","fromName":"Pete Wyckoff","fromEmail":"pw@padd.com","sentAt":"2012-01-02T23:05:51Z","receivedAt":"2012-01-02T23:05:51Z","isPatch":true,"sender":{"key":"pw@padd.com","avatar":null},"body":"From: Gary Gibbons <ggibbons@perforce.com>\n\nCorrect view sorting to support the Perforce order,\nwhere client views are ordered and later views\noverride earlier view mappings.\n\n[pw: one test now succeeds]\n\nSigned-off-by: Gary Gibbons <ggibbons@perforce.com>\nSigned-off-by: Pete Wyckoff <pw@padd.com>\n---\n contrib/fast-import/git-p4    |   11 +++++++++--\n t/t9809-git-p4-client-view.sh |    2 +-\n 2 files changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4\nindex c144c89..2fd4d3b 100755\n--- a/contrib/fast-import/git-p4\n+++ b/contrib/fast-import/git-p4\n@@ -1924,10 +1924,17 @@ class P4Sync(Command, P4UserMap):\n                     else:\n                         include = len(v)\n \n-                    temp[v] = (include, cv)\n+                    # store the View #number for sorting\n+                    # and the View string itself (this last for documentation)\n+                    temp[v] = (include, cv, int(k[4:]),k)\n \n         self.clientSpecDirs = temp.items()\n-        self.clientSpecDirs.sort( lambda x, y: abs( y[1][0] ) - abs( x[1][0] ) )\n+        # Perforce ViewNN with higher #numbers override those with lower\n+        # reverse sort on the View #number\n+        self.clientSpecDirs.sort( lambda x, y:  y[1][2] -  x[1][2]  )\n+        if self.verbose:\n+            for val in self.clientSpecDirs:\n+                    print \"clientSpecDirs: %s %s\" % (val[0],val[1])\n \n     def run(self, args):\n         self.depotPaths = []\ndiff --git a/t/t9809-git-p4-client-view.sh b/t/t9809-git-p4-client-view.sh\nindex cbf2213..06652cb 100755\n--- a/t/t9809-git-p4-client-view.sh\n+++ b/t/t9809-git-p4-client-view.sh\n@@ -133,7 +133,7 @@ test_expect_failure 'single file map' '\n \tgit_verify $files\n '\n \n-test_expect_failure 'later mapping takes precedence (entire repo)' '\n+test_expect_success 'later mapping takes precedence (entire repo)' '\n \tclient_view \"//depot/dir1/... //client/cli1/...\" \\\n \t\t    \"//depot/... //client/cli2/...\" &&\n \tfiles=\"cli2/dir1/file11 cli2/dir1/file12\n-- \n1.7.8.1.407.gd70cb\n"},{"id":"181846","messageId":"1325545554-16540-5-git-send-email-pw@padd.com","threadId":"29280","inReplyTo":"1325545554-16540-1-git-send-email-pw@padd.com","subject":"[PATCH 4/6] git-p4: support single file p4 client view maps","fromName":"Pete Wyckoff","fromEmail":"pw@padd.com","sentAt":"2012-01-02T23:05:52Z","receivedAt":"2012-01-02T23:05:52Z","isPatch":true,"sender":{"key":"pw@padd.com","avatar":null},"body":"From: Gary Gibbons <ggibbons@perforce.com>\n\nPerforce client views can map individual files,\nmapping one //depot file path to one //client file path.\nThese mappings contain no meta/masking characters.\nThis patch add support for these file maps to\nthe currently supported '...' view mappings.\n\n[pw: one test now suceeds]\n\nSigned-off-by: Gary Gibbons <ggibbons@perforce.com>\nSigned-off-by: Pete Wyckoff <pw@padd.com>\n---\n contrib/fast-import/git-p4    |   29 ++++++++++++++++++++---------\n t/t9809-git-p4-client-view.sh |    2 +-\n 2 files changed, 21 insertions(+), 10 deletions(-)\n\ndiff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4\nindex 2fd4d3b..8f28e0a 100755\n--- a/contrib/fast-import/git-p4\n+++ b/contrib/fast-import/git-p4\n@@ -1217,6 +1217,7 @@ class P4Sync(Command, P4UserMap):\n         self.cloneExclude = []\n         self.useClientSpec = False\n         self.clientSpecDirs = []\n+        self.haveSingleFileClientViews = False\n \n         if gitConfig(\"git-p4.syncFromOrigin\") == \"false\":\n             self.syncWithOrigin = False\n@@ -1274,6 +1275,16 @@ class P4Sync(Command, P4UserMap):\n             # will end up putting all foo/branch files into\n             #  branch/foo/\n             for val in self.clientSpecDirs:\n+                if self.haveSingleFileClientViews and len(path) == abs(val[1][0]) and path == val[0]:\n+                    # since 'path' is a depot file path, if it matches the LeftMap,\n+                    # then the View is a single file mapping, so use the entire rightMap\n+                    # first two tests above avoid the last == test for common cases\n+                    path =  val[1][1]\n+                    # now strip out the client (//client/...)\n+                    path = re.sub(\"^(//[^/]+/)\", '', path)\n+                    # the rest is local client path\n+                    return path\n+\n                 if path.startswith(val[0]):\n                     # replace the depot path with the client path\n                     path = path.replace(val[0], val[1][1])\n@@ -1905,19 +1916,19 @@ class P4Sync(Command, P4UserMap):\n                     # save the \"client view\"; i.e the RHS of the view\n                     # line that tells the client where to put the\n                     # files for this view.\n-                    cv = v[index+3:].strip() # +3 to remove previous '...'\n \n-                    # if the client view doesn't end with a\n-                    # ... wildcard, then we're going to mess up the\n-                    # output directory, so fail gracefully.\n-                    if not cv.endswith('...'):\n-                        print 'Sorry, client view in \"%s\" needs to end with wildcard' % (k)\n-                        sys.exit(1)\n-                    cv=cv[:-3]\n+                    # check for individual file mappings - those which have no '...'\n+                    if  index < 0 :\n+                        v,cv = v.strip().split()\n+                        v = v[start:]\n+                        self.haveSingleFileClientViews = True\n+                    else:\n+                        cv = v[index+3:].strip() # +3 to remove previous '...'\n+                        cv=cv[:-3]\n+                        v = v[start:index]\n \n                     # now save the view; +index means included, -index\n                     # means it should be filtered out.\n-                    v = v[start:index]\n                     if v.startswith(\"-\"):\n                         v = v[1:]\n                         include = -len(v)\ndiff --git a/t/t9809-git-p4-client-view.sh b/t/t9809-git-p4-client-view.sh\nindex 06652cb..1cc83c5 100755\n--- a/t/t9809-git-p4-client-view.sh\n+++ b/t/t9809-git-p4-client-view.sh\n@@ -191,7 +191,7 @@ test_expect_success 'exclusion wildcard, client rhs different (normal)' '\n \tgit_verify $files\n '\n \n-test_expect_failure 'exclusion single file' '\n+test_expect_success 'exclusion single file' '\n \tclient_view \"//depot/... //client/...\" \\\n \t\t    \"-//depot/dir2/file22 //client/file22\" &&\n \tfiles=\"dir1/file11 dir1/file12 dir2/file21\" &&\n-- \n1.7.8.1.407.gd70cb\n"},{"id":"181847","messageId":"1325545554-16540-6-git-send-email-pw@padd.com","threadId":"29280","inReplyTo":"1325545554-16540-1-git-send-email-pw@padd.com","subject":"[PATCH 5/6] git-p4: rewrite view handling","fromName":"Pete Wyckoff","fromEmail":"pw@padd.com","sentAt":"2012-01-02T23:05:53Z","receivedAt":"2012-01-02T23:05:53Z","isPatch":true,"sender":{"key":"pw@padd.com","avatar":null},"body":"The old code was not very complete or robust.  Redo it.\n\nThis new code should be useful for a few possible additions\nin the future:\n\n    - support for * and %%n wildcards\n    - allowing ... inside paths\n    - representing branch specs (not just client specs)\n    - tracking changes to views\n\nMark the remaining 12 tests in t9809 as fixed.\n\nSigned-off-by: Pete Wyckoff <pw@padd.com>\n---\n contrib/fast-import/git-p4    |  335 ++++++++++++++++++++++++++++++-----------\n t/t9809-git-p4-client-view.sh |   24 ++--\n 2 files changed, 258 insertions(+), 101 deletions(-)\n\ndiff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4\nindex 8f28e0a..3e1aa27 100755\n--- a/contrib/fast-import/git-p4\n+++ b/contrib/fast-import/git-p4\n@@ -1169,6 +1169,218 @@ class P4Submit(Command, P4UserMap):\n \n         return True\n \n+class View(object):\n+    \"\"\"Represent a p4 view (\"p4 help views\"), and map files in a\n+       repo according to the view.\"\"\"\n+\n+    class Path(object):\n+        \"\"\"A depot or client path, possibly containing wildcards.\n+           The only one supported is ... at the end, currently.\n+           Initialize with the full path, with //depot or //client.\"\"\"\n+\n+        def __init__(self, path, is_depot):\n+            self.path = path\n+            self.is_depot = is_depot\n+            self.find_wildcards()\n+            # remember the prefix bit, useful for relative mappings\n+            m = re.match(\"(//[^/]+/)\", self.path)\n+            if not m:\n+                die(\"Path %s does not start with //prefix/\" % self.path)\n+            prefix = m.group(1)\n+            if not self.is_depot:\n+                # strip //client/ on client paths\n+                self.path = self.path[len(prefix):]\n+\n+        def find_wildcards(self):\n+            \"\"\"Make sure wildcards are valid, and set up internal\n+               variables.\"\"\"\n+\n+            self.ends_triple_dot = False\n+            # There are three wildcards allowed in p4 views\n+            # (see \"p4 help views\").  This code knows how to\n+            # handle \"...\" (only at the end), but cannot deal with\n+            # \"%%n\" or \"*\".  Only check the depot_side, as p4 should\n+            # validate that the client_side matches too.\n+            if re.search(r'%%[1-9]', self.path):\n+                die(\"Can't handle %%n wildcards in view: %s\" % self.path)\n+            if self.path.find(\"*\") >= 0:\n+                die(\"Can't handle * wildcards in view: %s\" % self.path)\n+            triple_dot_index = self.path.find(\"...\")\n+            if triple_dot_index >= 0:\n+                if not self.path.endswith(\"...\"):\n+                    die(\"Can handle ... wildcard only at end of path: %s\" %\n+                        self.path)\n+                self.ends_triple_dot = True\n+\n+        def ensure_compatible(self, other_path):\n+            \"\"\"Make sure the wildcards agree.\"\"\"\n+            if self.ends_triple_dot != other_path.ends_triple_dot:\n+                 die(\"Both paths must end with ... if either does;\\n\" +\n+                     \"paths: %s %s\" % (self.path, other_path.path))\n+\n+        def match_wildcards(self, test_path):\n+            \"\"\"See if this test_path matches us, and fill in the value\n+               of the wildcards if so.  Returns a tuple of\n+               (True|False, wildcards[]).  For now, only the ... at end\n+               is supported, so at most one wildcard.\"\"\"\n+            if self.ends_triple_dot:\n+                dotless = self.path[:-3]\n+                if test_path.startswith(dotless):\n+                    wildcard = test_path[len(dotless):]\n+                    return (True, [ wildcard ])\n+            else:\n+                if test_path == self.path:\n+                    return (True, [])\n+            return (False, [])\n+\n+        def match(self, test_path):\n+            \"\"\"Just return if it matches; don't bother with the wildcards.\"\"\"\n+            b, _ = self.match_wildcards(test_path)\n+            return b\n+\n+        def fill_in_wildcards(self, wildcards):\n+            \"\"\"Return the relative path, with the wildcards filled in\n+               if there are any.\"\"\"\n+            if self.ends_triple_dot:\n+                return self.path[:-3] + wildcards[0]\n+            else:\n+                return self.path\n+\n+    class Mapping(object):\n+        def __init__(self, depot_side, client_side, overlay, exclude):\n+            # depot_side is without the trailing /... if it had one\n+            self.depot_side = View.Path(depot_side, is_depot=True)\n+            self.client_side = View.Path(client_side, is_depot=False)\n+            self.overlay = overlay  # started with \"+\"\n+            self.exclude = exclude  # started with \"-\"\n+            assert not (self.overlay and self.exclude)\n+            self.depot_side.ensure_compatible(self.client_side)\n+\n+        def __str__(self):\n+            c = \" \"\n+            if self.overlay:\n+                c = \"+\"\n+            if self.exclude:\n+                c = \"-\"\n+            return \"View.Mapping: %s%s -> %s\" % \\\n+                   (c, self.depot_side, self.client_side)\n+\n+        def map_depot_to_client(self, depot_path):\n+            \"\"\"Calculate the client path if using this mapping on the\n+               given depot path; does not consider the effect of other\n+               mappings in a view.  Even excluded mappings are returned.\"\"\"\n+            matches, wildcards = self.depot_side.match_wildcards(depot_path)\n+            if not matches:\n+                return \"\"\n+            client_path = self.client_side.fill_in_wildcards(wildcards)\n+            return client_path\n+\n+    #\n+    # View methods\n+    #\n+    def __init__(self):\n+        self.mappings = []\n+\n+    def append(self, view_line):\n+        \"\"\"Parse a view line, splitting it into depot and client\n+           sides.  Append to self.mappings, preserving order.\"\"\"\n+\n+        # Split the view line into exactly two words.  P4 enforces\n+        # structure on these lines that simplifies this quite a bit.\n+        #\n+        # Either or both words may be double-quoted.\n+        # Single quotes do not matter.\n+        # Double-quote marks cannot occur inside the words.\n+        # A + or - prefix is also inside the quotes.\n+        # There are no quotes unless they contain a space.\n+        # The line is already white-space stripped.\n+        # The two words are separated by a single space.\n+        #\n+        if view_line[0] == '\"':\n+            # First word is double quoted.  Find its end.\n+            close_quote_index = view_line.find('\"', 1)\n+            if close_quote_index <= 0:\n+                die(\"No first-word closing quote found: %s\" % view_line)\n+            depot_side = view_line[1:close_quote_index]\n+            # skip closing quote and space\n+            rhs_index = close_quote_index + 1 + 1\n+        else:\n+            space_index = view_line.find(\" \")\n+            if space_index <= 0:\n+                die(\"No word-splitting space found: %s\" % view_line)\n+            depot_side = view_line[0:space_index]\n+            rhs_index = space_index + 1\n+\n+        if view_line[rhs_index] == '\"':\n+            # Second word is double quoted.  Make sure there is a\n+            # double quote at the end too.\n+            if not view_line.endswith('\"'):\n+                die(\"View line with rhs quote should end with one: %s\" %\n+                    view_line)\n+            # skip the quotes\n+            client_side = view_line[rhs_index+1:-1]\n+        else:\n+            client_side = view_line[rhs_index:]\n+\n+        # prefix + means overlay on previous mapping\n+        overlay = False\n+        if depot_side.startswith(\"+\"):\n+            overlay = True\n+            depot_side = depot_side[1:]\n+\n+        # prefix - means exclude this path\n+        exclude = False\n+        if depot_side.startswith(\"-\"):\n+            exclude = True\n+            depot_side = depot_side[1:]\n+\n+        m = View.Mapping(depot_side, client_side, overlay, exclude)\n+        self.mappings.append(m)\n+\n+    def map_in_client(self, depot_path):\n+        \"\"\"Return the relative location in the client where this\n+           depot file should live.  Returns \"\" if the file should\n+           not be mapped in the client.\"\"\"\n+\n+        paths_filled = []\n+        client_path = \"\"\n+\n+        # look at later entries first\n+        for m in self.mappings[::-1]:\n+\n+            # see where will this path end up in the client\n+            p = m.map_depot_to_client(depot_path)\n+\n+            if p == \"\":\n+                # Depot path does not belong in client.  Must remember\n+                # this, as previous items should not cause files to\n+                # exist in this path either.  Remember that the list is\n+                # being walked from the end, which has higher precedence.\n+                # Overlap mappings do not exclude previous mappings.\n+                if not m.overlay:\n+                    paths_filled.append(m.client_side)\n+\n+            else:\n+                # This mapping matched; no need to search any further.\n+                # But, the mapping could be rejected if the client path\n+                # has already been claimed by an earlier mapping.\n+                already_mapped_in_client = False\n+                for f in paths_filled:\n+                    # this is View.Path.match\n+                    if f.match(p):\n+                        already_mapped_in_client = True\n+                        break\n+                if not already_mapped_in_client:\n+                    # Include this file, unless it is from a line that\n+                    # explicitly said to exclude it.\n+                    if not m.exclude:\n+                        client_path = p\n+\n+                # a match, even if rejected, always stops the search\n+                break\n+\n+        return client_path\n+\n class P4Sync(Command, P4UserMap):\n     delete_actions = ( \"delete\", \"move/delete\", \"purge\" )\n \n@@ -1216,8 +1428,7 @@ class P4Sync(Command, P4UserMap):\n         self.p4BranchesInGit = []\n         self.cloneExclude = []\n         self.useClientSpec = False\n-        self.clientSpecDirs = []\n-        self.haveSingleFileClientViews = False\n+        self.clientSpecDirs = None\n \n         if gitConfig(\"git-p4.syncFromOrigin\") == \"false\":\n             self.syncWithOrigin = False\n@@ -1268,30 +1479,7 @@ class P4Sync(Command, P4UserMap):\n \n     def stripRepoPath(self, path, prefixes):\n         if self.useClientSpec:\n-\n-            # if using the client spec, we use the output directory\n-            # specified in the client.  For example, a view\n-            #   //depot/foo/branch/... //client/branch/foo/...\n-            # will end up putting all foo/branch files into\n-            #  branch/foo/\n-            for val in self.clientSpecDirs:\n-                if self.haveSingleFileClientViews and len(path) == abs(val[1][0]) and path == val[0]:\n-                    # since 'path' is a depot file path, if it matches the LeftMap,\n-                    # then the View is a single file mapping, so use the entire rightMap\n-                    # first two tests above avoid the last == test for common cases\n-                    path =  val[1][1]\n-                    # now strip out the client (//client/...)\n-                    path = re.sub(\"^(//[^/]+/)\", '', path)\n-                    # the rest is local client path\n-                    return path\n-\n-                if path.startswith(val[0]):\n-                    # replace the depot path with the client path\n-                    path = path.replace(val[0], val[1][1])\n-                    # now strip out the client (//client/...)\n-                    path = re.sub(\"^(//[^/]+/)\", '', path)\n-                    # the rest is all path\n-                    return path\n+            return self.clientSpecDirs.map_in_client(path)\n \n         if self.keepRepoPath:\n             prefixes = [re.sub(\"^(//[^/]+/).*\", r'\\1', prefixes[0])]\n@@ -1441,19 +1629,17 @@ class P4Sync(Command, P4UserMap):\n         filesToDelete = []\n \n         for f in files:\n-            includeFile = True\n-            for val in self.clientSpecDirs:\n-                if f['path'].startswith(val[0]):\n-                    if val[1][0] <= 0:\n-                        includeFile = False\n-                    break\n+            # if using a client spec, only add the files that have\n+            # a path in the client\n+            if self.clientSpecDirs:\n+                if self.clientSpecDirs.map_in_client(f['path']) == \"\":\n+                    continue\n \n-            if includeFile:\n-                filesForCommit.append(f)\n-                if f['action'] in self.delete_actions:\n-                    filesToDelete.append(f)\n-                else:\n-                    filesToRead.append(f)\n+            filesForCommit.append(f)\n+            if f['action'] in self.delete_actions:\n+                filesToDelete.append(f)\n+            else:\n+                filesToRead.append(f)\n \n         # deleted files...\n         for f in filesToDelete:\n@@ -1892,60 +2078,31 @@ class P4Sync(Command, P4UserMap):\n \n \n     def getClientSpec(self):\n-        specList = p4CmdList( \"client -o\" )\n-        temp = {}\n-        for entry in specList:\n-            for k,v in entry.iteritems():\n-                if k.startswith(\"View\"):\n-\n-                    # p4 has these %%1 to %%9 arguments in specs to\n-                    # reorder paths; which we can't handle (yet :)\n-                    if re.search('%%\\d', v) != None:\n-                        print \"Sorry, can't handle %%n arguments in client specs\"\n-                        sys.exit(1)\n-                    if re.search('\\*', v) != None:\n-                        print \"Sorry, can't handle * mappings in client specs\"\n-                        sys.exit(1)\n-\n-                    if v.startswith('\"'):\n-                        start = 1\n-                    else:\n-                        start = 0\n-                    index = v.find(\"...\")\n-\n-                    # save the \"client view\"; i.e the RHS of the view\n-                    # line that tells the client where to put the\n-                    # files for this view.\n-\n-                    # check for individual file mappings - those which have no '...'\n-                    if  index < 0 :\n-                        v,cv = v.strip().split()\n-                        v = v[start:]\n-                        self.haveSingleFileClientViews = True\n-                    else:\n-                        cv = v[index+3:].strip() # +3 to remove previous '...'\n-                        cv=cv[:-3]\n-                        v = v[start:index]\n-\n-                    # now save the view; +index means included, -index\n-                    # means it should be filtered out.\n-                    if v.startswith(\"-\"):\n-                        v = v[1:]\n-                        include = -len(v)\n-                    else:\n-                        include = len(v)\n+        specList = p4CmdList(\"client -o\")\n+        if len(specList) != 1:\n+            die('Output from \"client -o\" is %d lines, expecting 1' %\n+                len(specList))\n+\n+        # dictionary of all client parameters\n+        entry = specList[0]\n+\n+        # just the keys that start with \"View\"\n+        view_keys = [ k for k in entry.keys() if k.startswith(\"View\") ]\n+\n+        # hold this new View\n+        view = View()\n \n-                    # store the View #number for sorting\n-                    # and the View string itself (this last for documentation)\n-                    temp[v] = (include, cv, int(k[4:]),k)\n+        # append the lines, in order, to the view\n+        for view_num in range(len(view_keys)):\n+            k = \"View%d\" % view_num\n+            if k not in view_keys:\n+                die(\"Expected view key %s missing\" % k)\n+            view.append(entry[k])\n \n-        self.clientSpecDirs = temp.items()\n-        # Perforce ViewNN with higher #numbers override those with lower\n-        # reverse sort on the View #number\n-        self.clientSpecDirs.sort( lambda x, y:  y[1][2] -  x[1][2]  )\n+        self.clientSpecDirs = view\n         if self.verbose:\n-            for val in self.clientSpecDirs:\n-                    print \"clientSpecDirs: %s %s\" % (val[0],val[1])\n+            for i, m in enumerate(self.clientSpecDirs.mappings):\n+                    print \"clientSpecDirs %d: %s\" % (i, str(m))\n \n     def run(self, args):\n         self.depotPaths = []\ndiff --git a/t/t9809-git-p4-client-view.sh b/t/t9809-git-p4-client-view.sh\nindex 1cc83c5..c9471d5 100755\n--- a/t/t9809-git-p4-client-view.sh\n+++ b/t/t9809-git-p4-client-view.sh\n@@ -107,7 +107,7 @@ test_expect_success 'wildcard ... only supported at end of spec' '\n \ttest_must_fail \"$GITP4\" clone --use-client-spec --dest=\"$git\" //depot\n '\n \n-test_expect_failure 'basic map' '\n+test_expect_success 'basic map' '\n \tclient_view \"//depot/dir1/... //client/cli1/...\" &&\n \tfiles=\"cli1/file11 cli1/file12\" &&\n \tclient_verify $files &&\n@@ -116,7 +116,7 @@ test_expect_failure 'basic map' '\n \tgit_verify $files\n '\n \n-test_expect_failure 'client view with no mappings' '\n+test_expect_success 'client view with no mappings' '\n \tclient_view &&\n \tclient_verify &&\n \ttest_when_finished cleanup_git &&\n@@ -124,7 +124,7 @@ test_expect_failure 'client view with no mappings' '\n \tgit_verify\n '\n \n-test_expect_failure 'single file map' '\n+test_expect_success 'single file map' '\n \tclient_view \"//depot/dir1/file11 //client/file11\" &&\n \tfiles=\"file11\" &&\n \tclient_verify $files &&\n@@ -144,7 +144,7 @@ test_expect_success 'later mapping takes precedence (entire repo)' '\n \tgit_verify $files\n '\n \n-test_expect_failure 'later mapping takes precedence (partial repo)' '\n+test_expect_success 'later mapping takes precedence (partial repo)' '\n \tclient_view \"//depot/dir1/... //client/...\" \\\n \t\t    \"//depot/dir2/... //client/...\" &&\n \tfiles=\"file21 file22\" &&\n@@ -159,7 +159,7 @@ test_expect_failure 'later mapping takes precedence (partial repo)' '\n #   dir1 cannot go to cli12 since it was filled by dir2\n #   dir1 also does not go to cli3, since the second rule\n #     noticed that it matched, but was already filled\n-test_expect_failure 'depot path matching rejected client path' '\n+test_expect_success 'depot path matching rejected client path' '\n \tclient_view \"//depot/dir1/... //client/cli3/...\" \\\n \t\t    \"//depot/dir1/... //client/cli12/...\" \\\n \t\t    \"//depot/dir2/... //client/cli12/...\" &&\n@@ -172,7 +172,7 @@ test_expect_failure 'depot path matching rejected client path' '\n \n # since both have the same //client/..., the exclusion\n # rule keeps everything out\n-test_expect_failure 'exclusion wildcard, client rhs same (odd)' '\n+test_expect_success 'exclusion wildcard, client rhs same (odd)' '\n \tclient_view \"//depot/... //client/...\" \\\n \t\t    \"-//depot/dir2/... //client/...\" &&\n \tclient_verify &&\n@@ -201,7 +201,7 @@ test_expect_success 'exclusion single file' '\n \tgit_verify $files\n '\n \n-test_expect_failure 'overlay wildcard' '\n+test_expect_success 'overlay wildcard' '\n \tclient_view \"//depot/dir1/... //client/cli/...\" \\\n \t\t    \"+//depot/dir2/... //client/cli/...\\n\" &&\n \tfiles=\"cli/file11 cli/file12 cli/file21 cli/file22\" &&\n@@ -211,7 +211,7 @@ test_expect_failure 'overlay wildcard' '\n \tgit_verify $files\n '\n \n-test_expect_failure 'overlay single file' '\n+test_expect_success 'overlay single file' '\n \tclient_view \"//depot/dir1/... //client/cli/...\" \\\n \t\t    \"+//depot/dir2/file21 //client/cli/file21\" &&\n \tfiles=\"cli/file11 cli/file12 cli/file21\" &&\n@@ -221,7 +221,7 @@ test_expect_failure 'overlay single file' '\n \tgit_verify $files\n '\n \n-test_expect_failure 'exclusion with later inclusion' '\n+test_expect_success 'exclusion with later inclusion' '\n \tclient_view \"//depot/... //client/...\" \\\n \t\t    \"-//depot/dir2/... //client/dir2/...\" \\\n \t\t    \"//depot/dir2/... //client/dir2incl/...\" &&\n@@ -232,7 +232,7 @@ test_expect_failure 'exclusion with later inclusion' '\n \tgit_verify $files\n '\n \n-test_expect_failure 'quotes on rhs only' '\n+test_expect_success 'quotes on rhs only' '\n \tclient_view \"//depot/dir1/... \\\"//client/cdir 1/...\\\"\" &&\n \tclient_verify \"cdir 1/file11\" \"cdir 1/file12\" &&\n \ttest_when_finished cleanup_git &&\n@@ -266,7 +266,7 @@ test_expect_success 'rename files to introduce spaces' '\n \t\t      \"dir 2/file21\" \"dir 2/file22\"\n '\n \n-test_expect_failure 'quotes on lhs only' '\n+test_expect_success 'quotes on lhs only' '\n \tclient_view \"\\\"//depot/dir 1/...\\\" //client/cdir1/...\" &&\n \tfiles=\"cdir1/file11 cdir1/file12\" &&\n \tclient_verify $files &&\n@@ -275,7 +275,7 @@ test_expect_failure 'quotes on lhs only' '\n \tclient_verify $files\n '\n \n-test_expect_failure 'quotes on both sides' '\n+test_expect_success 'quotes on both sides' '\n \tclient_view \"\\\"//depot/dir 1/...\\\" \\\"//client/cdir 1/...\\\"\" &&\n \tclient_verify \"cdir 1/file11\" \"cdir 1/file12\" &&\n \ttest_when_finished cleanup_git &&\n-- \n1.7.8.1.407.gd70cb\n"},{"id":"181848","messageId":"1325545554-16540-7-git-send-email-pw@padd.com","threadId":"29280","inReplyTo":"1325545554-16540-1-git-send-email-pw@padd.com","subject":"[PATCH 6/6] git-p4: view spec documentation","fromName":"Pete Wyckoff","fromEmail":"pw@padd.com","sentAt":"2012-01-02T23:05:54Z","receivedAt":"2012-01-02T23:05:54Z","isPatch":true,"sender":{"key":"pw@padd.com","avatar":null},"body":"\nSigned-off-by: Pete Wyckoff <pw@padd.com>\n---\n Documentation/git-p4.txt |   40 +++++++++++++++++++++++++++-------------\n 1 files changed, 27 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt\nindex f97b1c5..84f5b91 100644\n--- a/Documentation/git-p4.txt\n+++ b/Documentation/git-p4.txt\n@@ -230,12 +230,7 @@ git repository:\n \n --use-client-spec::\n \tUse a client spec to find the list of interesting files in p4.\n-\tThe client spec is discovered using 'p4 client -o' which checks\n-\tthe 'P4CLIENT' environment variable and returns a mapping of\n-\tdepot files to workspace files.  Note that a depot path is\n-\tstill required, but files found in the path that match in\n-\tthe client spec view will be laid out according to the client\n-\tspec.\n+\tSee the \"CLIENT SPEC\" section below.\n \n Clone options\n ~~~~~~~~~~~~~\n@@ -304,6 +299,27 @@ p4 revision specifier on the end:\n See 'p4 help revisions' for the full syntax of p4 revision specifiers.\n \n \n+CLIENT SPEC\n+-----------\n+The p4 client specification is maintained with the 'p4 client' command\n+and contains among other fields, a View that specifies how the depot\n+is mapped into the client repository.  Git-p4 can consult the client\n+spec when given the '--use-client-spec' option or useClientSpec\n+variable.\n+\n+The full syntax for a p4 view is documented in 'p4 help views'.  Git-p4\n+knows only a subset of the view syntax.  It understands multi-line\n+mappings, overlays with '+', exclusions with '-' and double-quotes\n+around whitespace.  Of the possible wildcards, git-p4 only handles\n+'...', and only when it is at the end of the path.  Git-p4 will complain\n+if it encounters an unhandled wildcard.\n+\n+The name of the client can be given to git-p4 in multiple ways.  The\n+variable 'git-p4.client' takes precedence if it exists.  Otherwise,\n+normal p4 mechanisms of determining the client are used:  environment\n+variable P4CLIENT, a file referenced by P4CONFIG, or the local host name.\n+\n+\n BRANCH DETECTION\n ----------------\n P4 does not have the same concept of a branch as git.  Instead,\n@@ -387,9 +403,7 @@ git-p4.host::\n \n git-p4.client::\n \tClient specified as an option to all p4 commands, with\n-\t'-c <client>'.  This can also be used as a way to find\n-\tthe client spec for the 'useClientSpec' option.\n-\tThe environment variable 'P4CLIENT' can be used instead.\n+\t'-c <client>', including the client spec.\n \n Clone and sync variables\n ~~~~~~~~~~~~~~~~~~~~~~~~\n@@ -417,10 +431,10 @@ git config --add git-p4.branchList main:branchB\n -------------\n \n git-p4.useClientSpec::\n-\tSpecify that the p4 client spec to be used to identify p4 depot\n-\tpaths of interest.  This is equivalent to specifying the option\n-\t'--use-client-spec'.  The variable 'git-p4.client' can be used\n-\tto specify the name of the client.\n+\tSpecify that the p4 client spec should be used to identify p4\n+\tdepot paths of interest.  This is equivalent to specifying the\n+\toption '--use-client-spec'.  See the \"CLIENT SPEC\" section above.\n+\tThis variable is a boolean, not the name of a p4 client.\n \n Submit variables\n ~~~~~~~~~~~~~~~~\n-- \n1.7.8.1.407.gd70cb\n"}]}