diff options
author | Tor Norbye <tnorbye@google.com> | 2013-08-29 10:40:28 -0700 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2013-08-29 10:40:35 -0700 |
commit | 932259520ebaedeb2ccf4b7594bad50c700963d7 (patch) | |
tree | 2b52334b8a019d9652625b1432deac11a7c4b31e /plugins/svn4idea/src/org/jetbrains/idea | |
parent | e47d04f1f804b9e725b768725da436af6788f19b (diff) | |
download | idea-932259520ebaedeb2ccf4b7594bad50c700963d7.tar.gz |
Snapshot 1c7917945d4706cdbb32b599f81abd05c0251e32 from idea/132.27 branch of git://git.jetbrains.org/idea/community.git
1c79179: 2013-08-29 Yann Cébron - DevKit: smart EP "implementation" highlighting/completion
f3a83bc: 2013-08-29 Roman Shevchenko - IDEA-112827 (NPE in error reporter)
464a45b: 2013-08-29 Anna Kozlova - move pin button to the right side of popup (IDEA-112435)
1635be8: 2013-08-29 Yann Cébron - Dom EPs: add some <with> tags
6bcac42: 2013-08-29 Roman Shevchenko - IDEA-112824 (suppress SVN logging for normal use)
16d0ba4: 2013-08-29 Anna Kozlova - NPE
285509c4c: 2013-08-28 Max Medvedev - IDEA-111110 Groovy: Introduce Field Refactoring doesn't suggest to choose destination class
5415af1: 2013-08-28 Yann Cébron - Merge remote-tracking branch 'origin/master'
8462f3b: 2013-08-28 Dmitry Jemerov - cleanup
2ad6100: 2013-08-28 Yann Cébron - Merge remote-tracking branch 'origin/master'
9de8bfc: 2013-08-28 Dmitry Jemerov - Merge branch 'master' of git://github.com/niktrop/intellij-community into pull92
6315415: 2013-08-28 Dmitry Jemerov - cleanup
7281b73: 2013-08-28 Dmitry Jemerov - Merge branch 'cjfm3' of git://github.com/max-kammerer/intellij-community into pull87
6e31350: 2013-08-28 Dmitry Jemerov - fix couple of issues with https://github.com/JetBrains/intellij-community/pull/94
3695aa0: 2013-08-28 Dmitry Jemerov - Merge branch 'master' of git://github.com/asedunov/intellij-community into pull94
ce2c15f: 2013-08-28 Yann Cébron - fix javadoc @see link
6794ead: 2013-08-28 Anna Kozlova - skip adverts when server doesn't accept provided info
851da00: 2013-08-28 Anna Kozlova - skip advs in tests and headless mode
cddbc28: 2013-08-28 Anna Kozlova - EA-48802 - assert: JavaFileManagerBase.findClass
1eafaae: 2013-08-28 Anna Kozlova - EA-49147 - NPE: UnusedDeclarationInspection.isReadObjectMethod
f1a2040: 2013-08-28 Anna Kozlova - EA-49155 - NPE: JavaChangeSignatureDialog.doCalculateSignature
2684709: 2013-08-28 Dmitry Jemerov - MalformedFormatStringInspectionTest fixed
ce8088e: 2013-08-28 Evgeny Gerashchenko - Removed extra checking for duplicate annotations in one file. It is performed when reading file anyway.
9d4c7ca: 2013-08-28 Dmitry Trofimov - Merge remote-tracking branch 'origin/master'
2fd8641: 2013-08-28 Eugene Kudelevsky - add possibility to setup lookup element for XML attribute values; IDEA-102167 layout_* attributes should go first
3b065b1: 2013-08-28 Dmitry Trofimov - Merge remote-tracking branch 'origin/master'
d8d9168: 2013-08-28 Dmitry Trofimov - Libs update.
28a95a6: 2013-08-28 Alexey Kudravtsev - compilation
d4a0d69: 2013-08-28 Alexey Kudravtsev - notnull
f9f8006: 2013-08-28 Alexey Kudravtsev - moved to appropriate package
4d058f8: 2013-08-28 Alexey Kudravtsev - cleanup
111301a: 2013-08-28 Alexey Kudravtsev - extra method
01e59b0: 2013-08-28 Alexey Kudravtsev - doc
da966ae: 2013-08-28 Alexey Kudravtsev - cleanup
7503616: 2013-08-28 Alexey Kudravtsev - add method to javaversionservice
7d6de4f: 2013-08-28 Alexey Kudravtsev - notnull
dee081f: 2013-08-27 Alexey Kudravtsev - statistics
dd327c0: 2013-08-28 Dmitry Trofimov - Focus fixes.
7231931: 2013-08-28 Vladimir Krivosheev - WEB-8988 Return "open in browser" in context menu
2102255: 2013-08-28 Roman Shevchenko - app: install-over range extended for next EAPs
ddad3bb: 2013-08-28 Dmitry Avdeev - IDEA-112728 Can't create new task from tasks menu in toolbar: no need to fix lost typing for Go To Task
a8160c2: 2013-08-28 Dmitry Avdeev - typo
7328cbd: 2013-08-28 Dmitry Avdeev - cleanup
abbdb5a: 2013-08-28 Dmitry Avdeev - cleanup
a5c8503: 2013-08-28 Dmitry Avdeev - do not allow empty task name
e8daf37: 2013-08-28 Dmitry Avdeev - simplified
dff28d3: 2013-08-28 Dmitry Avdeev - simplified
38513a8: 2013-08-28 Dmitry Avdeev - cleanup
c09dd55: 2013-08-28 Vladimir Krivosheev - VariablesGroup — avoid array copy
8f3c91f: 2013-08-28 Roman Shevchenko - java: correct character escaping in decompiler
9a19e30: 2013-08-28 Dmitry Trofimov - Fixed hiding and activating of the terminal (PY-10669).
df672ba: 2013-08-28 Dmitry Trofimov - Terminal system settings refactored.
4653b54: 2013-08-28 Anna Kozlova - unknown features equality fixed
2896270: 2013-08-28 Anna Kozlova - ensure read access
ccff3af: 2013-08-28 Anna Kozlova - suggest to download plugins by unknown run configurations
9c8a3d2: 2013-08-28 Konstantin Bulenkov - include os.arch and jdk build number in about
861984c: 2013-08-28 Denis Fokin - IDEA-108265. We should not do anything if an empty array is passed.
a6b3441: 2013-08-28 Sergey Simonchik - EA-49063 - AIOOBE: ScriptRunnerUtil$ScriptOutput.onTextAvailable
845ee5c: 2013-08-28 Vladimir Krivosheev - value nullability
36121a2: 2013-08-28 Konstantin Kolosovsky - Merge branch 'svn1_8_new'
1515b70: 2013-08-28 Mikhail Golubev - Merge remote-tracking branch 'origin/master'
33d684e0: 2013-08-28 Mikhail Golubev - IDEA-110012 Not all Redmine Issues Available on "Open Task" (Limited to 100?)
39899b1: 2013-08-28 Bas Leijdekkers - IDEA-112782 (Change signature dialog shows unexpected 'cannot resolve symbol' message)
e469928: 2013-08-28 Anna Kozlova - accept test config methods in non-test classes (IDEA-112537)
b53abed: 2013-08-27 Anna Kozlova - fix typo
3074f49: 2013-08-28 Dmitry Avdeev - IDEA-112781 Open YouTrack task: Create changelist doesn't work with SVN ?
43c8897: 2013-08-27 Dmitry Avdeev - cleanup
ff6217a: 2013-08-28 Kirill Likhodedov - Annotate overriding methods
8e2d0e1: 2013-08-28 Roman Shevchenko - terminal: platform's Guava should be good enough for the plugin
2055780: 2013-08-27 Roman Shevchenko - EA-49123 (do not load extensions from static initializer)
116dc30: 2013-08-27 Roman Shevchenko - EA-49235 (check proxy port)
2b4f96f: 2013-08-27 Roman Shevchenko - EA-49235 (code readability)
ed03bca: 2013-08-27 Roman Shevchenko - EA-49142 (NPE, cleanup)
e6053d0: 2013-08-27 Roman Shevchenko - EA-49102 (face user with printing errors)
a748474: 2013-08-28 Vladimir Krivosheev - 4.1.0. update netty (now it is not patched build, https://github.com/netty/netty/pull/1762)
3c60901: 2013-08-28 Konstantin Bulenkov - better selection for mixed languages
20decc3: 2013-08-28 Konstantin Bulenkov - fix selection for files with multiple languages
878ad26: 2013-08-27 Max Medvedev - IDEA-111100 Groovy: Introduce Variable/Parameter Refactorings don't suggest to replace occurrences if applied to expressions inside code blocks
8a60662: 2013-08-27 Max Medvedev - IDEA-110981 Groovy: "Split into declaration and assignment" intention leaves unnecessary "=" if applied to closures
77912c1: 2013-08-27 Max Medvedev - IDEA-111101 Groovy: In-Place Introduce Field: Alt+I mnemonic doesn't work in the refactoring preview
088f68e: 2013-08-27 Max Medvedev - IDEA-111027 Groovy: In-Place Introduce Variable: PIEAE at GrInplaceIntroducer.<init> on introducing a variable within one-line method/closure
a2210a4: 2013-08-27 Aleksei Sedunov - Extract inheritor candidate check into separate InheritanceChecker interface
f40be5e: 2013-08-27 Alexander Zolotov - Filter moduleAwareConfigurables by module
cf7704f: 2013-08-26 Alexander Zolotov - WEB-6452 SASS suggests functions at the top, instead of property values
165ccf7: 2013-08-27 Konstantin Kolosovsky - IDEA-94942 Fixed diff, annotate errors in history view after rename/move
7b6396a: 2013-08-27 Dmitry Jemerov - branch number 132
e176d25: 2013-08-27 Sergey Evdokimov - IDEA-112754 Maven import: NCDFE for org/jetbrains/plugins/groovy/util/ClassInstanceCache
43c49f6: 2013-08-27 Sergey Evdokimov - IDEA-112754 Maven import: NCDFE for org/jetbrains/plugins/groovy/util/ClassInstanceCache
377dd45: 2013-08-27 Konstantin Bulenkov - fix memory leak
203fb69: 2013-08-27 Sergey Simonchik - WEB-9011 Karma plugin ignored tests
3b896f2: 2013-08-27 Sergey Evdokimov - Make project unignored when new module is created by ignored project
9ae29c6: 2013-08-27 Sergey Evdokimov - Remove maven project from project tree when user deletes module. +review CR-IC-2084
0f0f39e: 2013-08-27 Vladimir Krivosheev - cleanup, Overrides
3dddec4: 2013-08-27 Vladimir Krivosheev - cleanup
a2fff55: 2013-08-27 Aleksey Pivovarov - Github: Add API function for loading Commit Comments
9bd1660: 2013-08-27 Aleksey Pivovarov - fix IndexOutOfBoundsException on inserting empty collection to empty model
46b8998: 2013-08-27 Nadya Zabrodina - exception during annotate copied file fixed
66640b2: 2013-08-27 Dmitry Trofimov - Merge remote-tracking branch 'origin/master'
c597bcc: 2013-08-27 Sergey Evdokimov - Remove maven project from project tree when user deletes module. +review CR-IC-2084
33e932a: 2013-08-27 Dmitry Trofimov - Merge remote-tracking branch 'origin/master'
7cc9cca: 2013-08-27 Vladislav.Soroka - IDEA-79466 gradle support should generate web module configuration
42e649d: 2013-08-27 Dmitry Jemerov - better names for couple of new classes added to API
5c70eaf: 2013-08-27 Dmitry Trofimov - Merge remote-tracking branch 'origin/master'
2e0f1fa: 2013-08-27 Dmitry Trofimov - Update lib.
dcc2c4b: 2013-08-27 Dmitry Trofimov - Override isRetina.
05b716b: 2013-08-27 nik - processing dependencies in JPS: @NotNull annotations and javadoc added
6314b6e: 2013-08-27 Dmitry Trofimov - Fixed for Retina.
9d1886c: 2013-08-27 Dmitry Avdeev - navigatable xsd documentation
0f389c5: 2013-08-27 Vladimir Krivosheev - hide internal class XValuePresenterAdapter
a0386eb: 2013-08-27 Mikhail Golubev - Merge remote-tracking branch 'origin/master'
53248dc: 2013-08-27 Vladimir Krivosheev - fix createPresenter
d0355b3: 2013-08-27 Sergey Evdokimov - Remove maven project from project tree when user deletes module. +review CR-IC @Anton.Makeev
fb48f62: 2013-08-27 Sergey Evdokimov - Optimization of MavenProjectsTree.isManagedFile()
6b123aa: 2013-08-27 nik - JPS dependencies enumerator: convenient method added
d4579e1: 2013-08-27 Dmitry Avdeev - UrlPsiReference promoted
3b1feea: 2013-08-27 Dmitry Avdeev - cleanup
ee18443: 2013-08-27 Dmitry Avdeev - cleanup
ca2484e: 2013-08-27 Dmitry Avdeev - cleanup
c9e045b: 2013-08-27 Vladimir Krivosheev - 1) methods "void setPresentation(@NonNls String name, @Nullable Icon icon, @NonNls @Nullable String type, @NonNls @NotNull String separator, @NonNls @NotNull String value, boolean hasChildren);" and "void setPresentation(@NonNls String name, @Nullable Icon icon, @NonNls @Nullable String type, @NonNls @NotNull String value, boolean hasChildren);"
b38d58a: 2013-08-27 Konstantin Kolosovsky - IDEA-94942 Fixed treating svn client warnings as errors
8cab12c: 2013-08-27 Dmitry Jemerov - separate UI and non-UI parts of MalformedFormatStringInspection
93e3fa6: 2013-08-20 Jason Holmes - Custom "Malformed format string" inspection
b9f6fde: 2013-08-27 nik - actions to mark/unmark roots in Project View refactored to support custom root types
0455e46: 2013-08-27 Aleksey Pivovarov - Github: remove useless listener
7f40613: 2013-08-27 Aleksey Pivovarov - Github: reset User on token change
c8a5402: 2013-08-27 Aleksey Pivovarov - Github: change layout
89769be: 2013-08-27 Aleksey Pivovarov - Github: add test
0d8ab04: 2013-08-27 Aleksey Pivovarov - Github: comment
cba103c: 2013-08-27 Dmitry Trofimov - Merge remote-tracking branch 'origin/master'
d6ca049: 2013-08-27 Anna Kozlova - allow autoPopup after custom symbols (IDEA-112571)
a70c338: 2013-08-27 Konstantin Bulenkov - IDEA-110846 File Structure pop-up doesn't work properly for template languages
4896775: 2013-08-27 Konstantin Bulenkov - recent files duplicates
05835fa: 2013-08-27 Fedor Korotkov - WEB-6328 Add support for HTML5 <main> element
2407d7b: 2013-08-27 Anna Kozlova - allow to call getValue without readAction as it was before
29cb25d: 2013-08-27 Dmitry Avdeev - fixing the leak
7d3932a: 2013-08-27 Dmitry Avdeev - IDEA-112708 Static classes in JSP class block are considered an error
f6dbce2: 2013-08-26 Mikhail Golubev - IDEA-112605 Task management: can't add Generic server: NoClassDefFoundError: XPathFileType
82c1dc1: 2013-08-27 Konstantin Kolosovsky - Merge branch 'svn1_8_new'
08d46f1: 2013-08-27 Alexey Kudravtsev - highlightVisitor moves and cleanup
e0fec9d: 2013-08-27 Alexey Kudravtsev - Merge remote-tracking branch 'origin/master'
e9b1dfc: 2013-08-27 Anna Kozlova - dumb smart lambda completion (IDEA-112553)
6da30a4: 2013-08-26 Alexey Kudravtsev - calculate column/offset: optimisation of the no-tabs case
c0990c4: 2013-08-26 Alexey Kudravtsev - race conditions?
135e250: 2013-08-26 Alexey Kudravtsev - notnull, cleanup
75b28ba: 2013-08-26 Alexey Kudravtsev - file was not rehighlighted on some changes
5647d35: 2013-08-26 Sergey Evdokimov - Make myManagedFilesPaths a Set to avoid duplication.
680dd76: 2013-08-27 Aleksey Pivovarov - Github: fix memory leak on Exception in setUp();
e3092b7: 2013-08-27 Dmitry Avdeev - IDEA-112611 Task management: DVCS: closing a task fails: "Cannot delete the branch master"
d12d4fe: 2013-08-27 Anna Kozlova - logging for EA-49099 - PIEAE: PsiAnchor$StubIndexReference.getStartOffset
3cc53fe: 2013-08-27 Anna Kozlova - revert changes in api
10fbef9: 2013-08-27 Roman Shevchenko - Test data updated
a5455ba: 2013-08-27 Dmitry Trofimov - Merge remote-tracking branch 'origin/master'
8ed3ae8: 2013-08-27 Dmitry Trofimov - Updated lib.
e6df583: 2013-08-27 Dmitry Trofimov - Added guava lib to terminal.
1853090: 2013-08-27 Dmitry Trofimov - Draw image Retina support.
a7ebcaf: 2013-08-27 Dmitry Trofimov - Open session action moved to constructor.
5176bfa: 2013-08-26 Sergey Evdokimov - Fix maven tests
291f740: 2013-08-26 Vladimir Krivosheev - we must check if any port free too
18d4be6: 2013-08-26 Vassiliy Kudryashov - IDEA-70769 Settings panel: increase speed of scrollbars
0c76aca: 2013-08-26 Vassiliy Kudryashov - IDEA-112524 Working directory for default rake tasks is changed to $MODULE_DIR$ after project's reopening
e2b7f6b: 2013-08-26 Konstantin Bulenkov - pattern dependent delay
1916142: 2013-08-26 Vladimir Krivosheev - cleanup
3da9dcd: 2013-08-26 Vladimir Krivosheev - done: custom binary request handler
e02eba9: 2013-08-26 Aleksey Pivovarov - Github: do not produce dozens of notifications
8e41d59: 2013-08-26 Aleksey Pivovarov - Simplify
78c800d: 2013-08-26 Aleksey Pivovarov - Github: respect 'max' parameter
3afb19f: 2013-08-26 Maxim.Mossienko - proper versioning of stub index when persistent enumerator version changes
c853e9e: 2013-08-26 Eugene Kudelevsky - IDEA-112376 add "importFilter" extension point to force using FQN when importing class
31281a9: 2013-08-26 Kirill Likhodedov - [git] Don't write empty lines to LOG.debug.
493b9fb: 2013-08-26 Dmitry Trofimov - Merge remote-tracking branch 'origin/master'
60384ba: 2013-08-26 Dmitry Trofimov - Enable Run local terminal action for Windows.
bfa8e61: 2013-08-26 Dmitry Jemerov - stupid typo fixed
04a76c3: 2013-08-26 Anna Kozlova - IDEA-112555 Bad code is green with method references on instance
8f5139d: 2013-08-26 Denis Fokin - IDEA-108265. Now user is asked whether the project should be opened in a new frame.
da958ab: 2013-08-26 Natalia Ukhorskaya - Decompile chars, bytes and shorts correctly
8008709: 2013-08-26 Roman Shevchenko - java: incorrect annotation decoding fixed
9f07ea5: 2013-08-26 Roman Shevchenko - logging
e07c905: 2013-08-26 Dmitry Trofimov - Merge remote-tracking branch 'origin/master'
e81832d: 2013-08-26 Dmitry Trofimov - This update fixes pty on windows.
e36607b: 2013-08-26 Konstantin Bulenkov - new renderer
9ea47a5: 2013-08-26 nik - source roots editors: obtain icons for content tree from extension
d52dfb8: 2013-08-26 nik - constants moved
710d3ba: 2013-08-26 niktrop - Code style fixed
aa8832d: 2013-08-26 Sergey Simonchik - code style: two subsequent ifs merged to reduce inner indent
ed88487: 2013-08-26 Konstantin Kolosovsky - IDEA-94942 Provide detailed error messages to user instead of general ones
e0df7a3: 2013-08-26 Vassiliy Kudryashov - IDEA-112524 Working directory for default rake tasks is changed to $MODULE_DIR$ after project's reopening
e60a55b: 2013-08-26 nik - source roots editors refactored: root type specific UI moved to extension
928ea20: 2013-08-26 Mikhail Golubev - Merge remote-tracking branch 'origin/master'
70d526d: 2013-08-26 Anna Kozlova - guardedBy itself support (IDEA-112565)
2e23cb8: 2013-08-26 Anna Kozlova - restore bytecode viewer for java classes
89dffc9: 2013-08-26 Anna Kozlova - cal property name: accept without any other checks non-letter prefixes (IDEA-112585)
bab044f: 2013-08-26 Vladimir Krivosheev - restore Alarm.cancelRequest
dd817dc: 2013-08-26 Sergey Simonchik - simplification
7046dc4: 2013-08-26 niktrop - Some refactoring for reusing GenerateEquals UI in scala plugin
44e4219: 2013-08-26 Sergey Evdokimov - IDEA-112529 Maven: code completion could work in file path value with property references
867be29: 2013-08-26 Denis Fokin - Jumplist libraries changes. This is a release version of dll with eliminated MSVCRT dependencies.
7b0029d: 2013-08-26 Anton Makeev - CIDR: language tests in windows +review CR-OC @micha
9bef3a2: 2013-08-26 Konstantin Kolosovsky - IDEA-94942 Updated cleanup behavior after previous command failed
8447776: 2013-08-26 Mikhail Golubev - Remove SelectorBasedResponseHandler#getSelectorFileType, delegate to #getResponseType instead. Add missing @NotNull annotation, update doc comments.
d7ecb44: 2013-08-26 Anna Kozlova - treat classes with before/after methods as test classes for bad declared exceptions (IDEA-112537)
4be2bcf: 2013-08-26 Anna Kozlova - restore suggestion to remove 'abstract' when method has body
c57c308: 2013-08-26 Anna Kozlova - local can be final inside lambda body (IDEA-112630)
b8170a6: 2013-08-26 Vladimir Krivosheev - isValuesCustomSorted, add or not SortValuesAction (alphabetically sort) TODO: this action should be moved to "Variables" as gear action
b56b744: 2013-08-26 Mikhail Golubev - Merge remote-tracking branch 'origin/master'
ba7e194: 2013-08-23 Mikhail Golubev - Migrate Assembla to new GenericRepository
dd0885d: 2013-08-23 Mikhail Golubev - Refactor TemplateVariable
aa94a3c: 2013-08-23 Mikhail Golubev - Add several tests of date parsing
0e7e61e: 2013-08-23 Mikhail Golubev - Reflective PsiElements creation in JqlParserDefinition
a5f3011: 2013-08-26 Konstantin Kolosovsky - IDEA-94942 Short refactoring (removed duplication)
01cd38b: 2013-08-26 Sergey Simonchik - unnecessary line removed
7c34a2a: 2013-08-26 Dmitry Avdeev - IDEA-60895 No completion for enumerated and boolean values of xml tags
8b83a30: 2013-08-26 Vladislav.Soroka - gradle: cosmetic UI fix
8ee6a76: 2013-08-26 Roman Shevchenko - Convenient debug logging
cde5373: 2013-08-26 Dmitry Avdeev - IDEA-60895 No completion for enumerated and boolean values of xml tags
75d9b47: 2013-08-24 Max Medvedev - IDEA-112621 Groovy: Remove explicit type declaration intention
d1c29fb: 2013-08-23 Max Medvedev - separate visit methods for all types of classes, enums, interfaces, annotation types, and anonymous classes
5eca3d0: 2013-08-23 Max Medvedev - Byte code viewer shows byte code for groovy scripts
c96d27f: 2013-08-23 Max Medvedev - Convert anonymous class to closure: don't insert 'as Runnable' if groovy version is at least 2.2
7e993a0: 2013-08-23 Max Medvedev - IDEA-112560 process only applicable mixins to a ref
c33dc4a: 2013-08-26 Roman Shevchenko - platform: unified loading of system libraries (done right)
d492a6f: 2013-08-25 Roman Shevchenko - logging
2f2b546: 2013-08-25 Roman Shevchenko - IDEA-112462 (allow plugins to extend lib search path)
2a39bf2: 2013-08-25 Maxim.Mossienko - restart lexer from 0 offset when searching from start
a0858dbf: 2013-08-25 Maxim.Mossienko - 20% more compact compiler caches (-50M for IDEA project)
5ce3373: 2013-08-25 Maxim.Mossienko - IDEA-111918 Find: comments / string literals only: just 1 entry is found in each comment or literal
68ffc65: 2013-08-25 Maxim.Mossienko - faster contol + shift + N / control + N by default
eb67af1: 2013-08-24 Vassiliy Kudryashov - IDEA-107413 Cannot drag'n'drop more than one item in Changes View
d5ed7b5: 2013-08-24 nik - store properties of source folder in JPS element
b3dd357: 2013-08-24 nik - typo
5b8b1ff: 2013-08-24 Kirill Likhodedov - Merge remote-tracking branch 'origin/master'
0dd284a: 2013-08-24 Kirill Likhodedov - Better assertion error in the DefaultLogger
3146c0b: 2013-08-23 Bas Leijdekkers - foreach can also initialize field
6356a11: 2013-08-23 Konstantin Bulenkov - completely refacrored
6ad3452: 2013-08-23 Anna Kozlova - show conflict on invert boolean and method references (IDEA-112572)
4726276: 2013-08-23 Anna Kozlova - extract method from lambda body: accept parameters of parent method (IDEA-112570)
16a5e32: 2013-08-23 Gregory.Shrago - EditorEx: permanent header API
edc3497: 2013-08-23 Konstantin Kolosovsky - IDEA-94942 Content retrieval from svn refactored to ClientFactory model
0f40312: 2013-08-23 Mikhail Golubev - Change JqlQuery methods, JqlTerminalClause should extend JqlClause
c335f2b: 2013-08-23 Mikhail Golubev - IDEA-111811 Task management: JIRA: JQL: code completion suggests nothing after closing parenthesis
6e6972a: 2013-08-23 Konstantin Kolosovsky - IDEA-94942 Annotate action
e9d5412: 2013-08-23 Mikhail Golubev - Update description of NATIVE_SEARCH feature in TaskRepository
3c948a8: 2013-08-22 Konstantin Kolosovsky - IDEA-94942 Code cleanup - unused parameters removed
64706df: 2013-08-22 Konstantin Kolosovsky - IDEA-112184
0e41cad: 2013-08-22 Konstantin Kolosovsky - IDEA-94942 Simple property client to fix SvnMergeProvider.isBinary implementation
8091bb0: 2013-08-22 Konstantin Kolosovsky - IDEA-94942 Implemented file conflicts resolving Updated "svn info" result parsing
040f405: 2013-08-21 Konstantin Kolosovsky - IDEA-94942 SvnBindClient - unsupported methods removed
cef0440: 2013-08-21 Konstantin Kolosovsky - IDEA-94942 "bindSvn" module classes moved to "svn4idea" SvnBindClient unsupported methods will be removed in next commit (to track change like "rename" instead of "delete" + "add" to preserve history)
6c36a93: 2013-08-20 Konstantin Kolosovsky - IDEA-112184 Added logging to detect authentication issues
928c01e: 2013-08-07 Konstantin Kolosovsky - IDEA-94942 - Basic svn 1.8 test support
d880599: 2013-08-20 Konstantin Kolosovsky - IDEA-94942 Small text fixes Ignoring tests for old/not used functionality
71384c1: 2013-08-19 Konstantin Kolosovsky - IDEA-94942 Copy and move actions
a5f7e7c: 2013-08-19 Konstantin Kolosovsky - IDEA-94942 Delete action
e197c08: 2013-08-19 Konstantin Kolosovsky - IDEA-94942 Fixed authentication for svn protocol
c28c127: 2013-08-19 Konstantin Kolosovsky - IDEA-94942 Short add, revert commands refactoring
e06a346: 2013-08-19 Konstantin Kolosovsky - IDEA-94942 Revert action Fixed status command for single file Fixed status result parsing for normal (non-modified versioned) file
0f66f9d: 2013-08-19 Konstantin Kolosovsky - IDEA-94942 Logging and comments for some commands
c928fe5: 2013-08-16 Konstantin Kolosovsky - IDEA-94942 Force command line client usage if working copy of svn 1.8 format detected
d6e4e38: 2013-08-16 Konstantin Kolosovsky - IDEA-94942 Fixed "add" action output parsing for binary files
1236be5: 2013-08-15 Konstantin Kolosovsky - IDEA-94942 Small refactoring and fixes after review
e05b576: 2013-08-15 Konstantin Kolosovsky - IDEA-94942 "Add" action for files and directories
ccf6085: 2013-08-15 Konstantin Kolosovsky - IDEA-94942 "Subversion" -> "Show History" for files and folders
aba7390: 2013-08-14 Konstantin Kolosovsky - IDEA-94942 Add command refactored
555d597: 2013-08-14 Konstantin Kolosovsky - IDEA-94942 Fixing status, info commands to use correct arguments
b083f31: 2013-08-14 Konstantin Kolosovsky - IDEA-94942 Diff provider (without revision properties) Small command refactoring
f657dc8: 2013-08-13 Konstantin Kolosovsky - IDEA-94942 Authentication updates Authentication for remote status command Result parsing for remote info command 2 way SSL support (could be issues with ordinary password entering after 2 way SSL)
2785122: 2013-07-29 Konstantin Kolosovsky - IDEA-94942 initial svn 1.8 support with already existing command line functionality
857bfd5: 2013-08-12 max-kammerer - Update CoreJavaFileManagerTest.java
13539bc: 2013-08-09 Mikhael Bogdanov - CoreJavaFileManager.findClass: properly resolve $ in inner class names
Change-Id: Ica3d3d647183983bcd88ce2fb3450deb86343cdb
Diffstat (limited to 'plugins/svn4idea/src/org/jetbrains/idea')
100 files changed, 4594 insertions, 912 deletions
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/ForNestedRootChecker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/ForNestedRootChecker.java index 4559a3e603e2..6a62276b3a1a 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/ForNestedRootChecker.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/ForNestedRootChecker.java @@ -49,11 +49,9 @@ public class ForNestedRootChecker { private static class UrlConstructor { final SvnVcs myVcs; - final SVNWCClient myClient; private UrlConstructor(final SvnVcs vcs) { myVcs = vcs; - myClient = myVcs.createWCClient(); } @Nullable diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/RootsToWorkingCopies.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/RootsToWorkingCopies.java index 6cd0df2744d1..5ca7f9eaa39b 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/RootsToWorkingCopies.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/RootsToWorkingCopies.java @@ -123,52 +123,31 @@ public class RootsToWorkingCopies implements VcsListener { @Nullable private WorkingCopy calculateRoot(final VirtualFile root) { + File workingCopyRoot = SvnUtil.getWorkingCopyRootNew(new File(root.getPath())); WorkingCopy workingCopy = null; - final File ioFile = new File(root.getPath()); - File workingCopyRoot = null; - try { - workingCopyRoot = SVNWCUtil.getWorkingCopyRoot(ioFile, true); - if (workingCopyRoot != null) { - // ok to use low-level 1.6 API, 1.7 is checked below - SVNWCAccess wcAccess = SVNWCAccess.newInstance(null); - try { - wcAccess.probeOpen(workingCopyRoot, false, 0); - SVNEntry entry = wcAccess.getVersionedEntry(workingCopyRoot, false); - final SVNURL url = entry.getSVNURL(); - if (url != null) { - workingCopy = new WorkingCopy(workingCopyRoot, url, false); - } - } finally { - wcAccess.close(); - } - } - } - catch (SVNException e) { - // - } - if (workingCopy == null) { - workingCopyRoot = SvnUtil.getWcCopyRootIf17(ioFile, null); - if (workingCopyRoot != null) { - final SVNInfo svnInfo; - try { - svnInfo = SvnVcs.getInstance(myProject).createWCClient().doInfo(workingCopyRoot, SVNRevision.UNDEFINED); - workingCopy = new WorkingCopy(workingCopyRoot, svnInfo.getURL(), true); - } - catch (SVNException e) { - // - } + + if (workingCopyRoot != null) { + final SVNInfo svnInfo = myVcs.getInfo(workingCopyRoot); + + if (svnInfo != null && svnInfo.getURL() != null) { + workingCopy = new WorkingCopy(workingCopyRoot, svnInfo.getURL(), true); } } + + return registerWorkingCopy(root, workingCopy); + } + + private WorkingCopy registerWorkingCopy(@NotNull VirtualFile root, @Nullable WorkingCopy resolvedWorkingCopy) { synchronized (myLock) { - if (workingCopy == null) { + if (resolvedWorkingCopy == null) { myRootMapping.remove(root); myUnversioned.add(root); } else { myUnversioned.remove(root); - myRootMapping.put(root, workingCopy); + myRootMapping.put(root, resolvedWorkingCopy); } } - return workingCopy; + return resolvedWorkingCopy; } public void clear() { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAbstractWriteOperationLocks.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAbstractWriteOperationLocks.java index b4fd10492dfb..29e87422bb53 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAbstractWriteOperationLocks.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAbstractWriteOperationLocks.java @@ -36,6 +36,7 @@ import java.util.concurrent.locks.ReentrantLock; * Date: 10/19/12 * Time: 12:09 PM */ +// TODO: Such locking functionality is not required anymore. Likely to be removed (together with SvnProxies). public abstract class SvnAbstractWriteOperationLocks { private final long myTimeout; private final static Map<String, Lock> myLockMap = new HashMap<String, Lock>(); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationNotifier.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationNotifier.java index 8a94f0076acf..4efcce6f0db6 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationNotifier.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationNotifier.java @@ -350,7 +350,9 @@ public class SvnAuthenticationNotifier extends GenericNotifierImpl<SvnAuthentica } SvnInteractiveAuthenticationProvider.clearCallState(); try { + // start svnkit authentication cycle SvnVcs.getInstance(project).createWCClient(manager).doInfo(url, SVNRevision.UNDEFINED, SVNRevision.HEAD); + //SvnVcs.getInstance(project).getInfo(url, SVNRevision.HEAD, manager); } catch (SVNAuthenticationException e) { log(e); return false; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties index c306386eb0f8..61a5bb26f663 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties @@ -537,6 +537,7 @@ dialog.show.svn.map.table.version14.text=1.4 dialog.show.svn.map.table.version15.text=1.5 dialog.show.svn.map.table.version16.text=1.6 dialog.show.svn.map.table.version17.text=1.7 +dialog.show.svn.map.table.version18.text=1.8 action.change.wcopy.format.task.title=Convert working copy format action.change.wcopy.format.task.progress.text=Converting working copy at {0} format from {1} to {2} action.change.wcopy.format.after.change.settings=Selected working copy format ({0}) is older than upgrade mode selected in Project Settings ({1}).\ diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProvider.java index dc8cbb1cce8b..b28ebd53dde2 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProvider.java @@ -85,7 +85,7 @@ public class SvnChangeProvider implements ChangeProvider { statusReceiver.addListener(context); statusReceiver.addListener(nestedCopiesBuilder); - final SvnRecursiveStatusWalker walker = new SvnRecursiveStatusWalker(myVcs.getProject(), statusReceiver.getMulticaster(), partner); + final SvnRecursiveStatusWalker walker = new SvnRecursiveStatusWalker(myVcs, statusReceiver.getMulticaster(), partner); for (FilePath path : zipper.getRecursiveDirs()) { walker.go(path, SVNDepth.INFINITY); @@ -180,7 +180,7 @@ public class SvnChangeProvider implements ChangeProvider { public void getChanges(final FilePath path, final boolean recursive, final ChangelistBuilder builder) throws SVNException { final SvnChangeProviderContext context = new SvnChangeProviderContext(myVcs, builder, null); final StatusWalkerPartnerImpl partner = new StatusWalkerPartnerImpl(myVcs, ProgressManager.getInstance().getProgressIndicator()); - final SvnRecursiveStatusWalker walker = new SvnRecursiveStatusWalker(myVcs.getProject(), context, partner); + final SvnRecursiveStatusWalker walker = new SvnRecursiveStatusWalker(myVcs, context, partner); walker.go(path, recursive ? SVNDepth.INFINITY : SVNDepth.IMMEDIATES); processCopiedAndDeleted(context, null); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java index 40c34d394590..9689dcee4a71 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java @@ -259,23 +259,15 @@ class SvnChangeProviderContext implements StatusReceiver { public void addModifiedNotSavedChange(final VirtualFile file) throws SVNException { final FilePath filePath = new FilePathImpl(file); - final SVNInfo svnInfo; - try { - svnInfo = myVcs.createWCClient().doInfo(new File(file.getPath()), SVNRevision.UNDEFINED); + final SVNInfo svnInfo = myVcs.getInfo(file); + + if (svnInfo != null) { + final SVNStatus svnStatus = new SVNStatus(); + svnStatus.setRevision(svnInfo.getRevision()); + myChangelistBuilder.processChangeInList( + createChange(SvnContentRevision.createBaseRevision(myVcs, filePath, svnInfo.getRevision()), CurrentContentRevision.create(filePath), + FileStatus.MODIFIED, svnStatus), (String)null, SvnVcs.getKey()); } - catch (SVNException e) { - final SVNErrorCode errorCode = e.getErrorMessage().getErrorCode(); - if (SVNErrorCode.WC_PATH_NOT_FOUND.equals(errorCode) || - SVNErrorCode.UNVERSIONED_RESOURCE.equals(errorCode) || SVNErrorCode.WC_NOT_WORKING_COPY.equals(errorCode)) { - return; - } - throw e; - } - final SVNStatus svnStatus = new SVNStatus(); - svnStatus.setRevision(svnInfo.getRevision()); - myChangelistBuilder.processChangeInList(createChange(SvnContentRevision.createBaseRevision(myVcs, filePath, svnInfo.getRevision()), - CurrentContentRevision.create(filePath), FileStatus.MODIFIED, svnStatus), (String) null, - SvnVcs.getKey()); } private void checkSwitched(final FilePath filePath, final ChangelistBuilder builder, final SVNStatus status, diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangelistListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangelistListener.java index ee326a42afb2..51d5dc486c51 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangelistListener.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangelistListener.java @@ -176,11 +176,9 @@ public class SvnChangelistListener implements ChangeListListener { } @Nullable - public static String getCurrentMapping(final Project project, final File file) { - final SvnVcs vcs = SvnVcs.getInstance(project); - final SVNStatusClient statusClient = vcs.createStatusClient(); + public static String getCurrentMapping(final SvnVcs vcs, final File file) { try { - final SVNStatus status = statusClient.doStatus(file, false); + final SVNStatus status = vcs.getFactory(file).createStatusClient().doStatus(file, false); return status == null ? null : status.getChangelistName(); } catch (SVNException e) { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfigurable.form b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfigurable.form index e4f0e7c8ae7e..55f7512fbb81 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfigurable.form +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfigurable.form @@ -18,7 +18,7 @@ <properties/> <border type="none"/> <children> - <grid id="2ae3a" layout-manager="GridLayoutManager" row-count="6" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <grid id="2ae3a" layout-manager="GridLayoutManager" row-count="5" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> <margin top="0" left="0" bottom="0" right="0"/> <constraints> <tabbedpane title="General"/> @@ -29,7 +29,7 @@ <grid id="a4729" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> <margin top="0" left="0" bottom="0" right="0"/> <constraints> - <grid row="3" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + <grid row="2" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> </constraints> <properties/> <border type="none"/> @@ -87,7 +87,7 @@ </grid> <component id="df054" class="javax.swing.JCheckBox" binding="myUseDefaultCheckBox"> <constraints> - <grid row="2" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + <grid row="1" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> </constraints> <properties> <text resource-bundle="org/jetbrains/idea/svn/SvnBundle" key="checkbox.configure.use.system.default.configuration.directory"/> @@ -96,7 +96,7 @@ <grid id="137aa" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> <margin top="0" left="0" bottom="0" right="0"/> <constraints> - <grid row="5" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + <grid row="4" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> </constraints> <properties/> <border type="none"/> @@ -123,36 +123,29 @@ </grid> <hspacer id="e8f24"> <constraints> - <grid row="5" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/> + <grid row="4" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/> </constraints> </hspacer> <vspacer id="aa6b2"> <constraints> - <grid row="4" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/> + <grid row="3" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/> </constraints> </vspacer> - <component id="300b5" class="javax.swing.JLabel"> - <constraints> - <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> - </constraints> - <properties> - <text value="Subversion 1.7 Acceleration"/> - </properties> - </component> <grid id="12735" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> <margin top="0" left="0" bottom="0" right="0"/> <constraints> - <grid row="1" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + <grid row="0" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> </constraints> <properties/> <border type="none"/> <children> <component id="3ecd1" class="com.intellij.ui.components.JBCheckBox" binding="myWithCommandLineClient"> <constraints> - <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="2" use-parent-layout="false"/> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> </constraints> <properties> - <text value="with command line client:"/> + <margin top="2" left="3" bottom="2" right="3"/> + <text value="Use command line client:"/> </properties> </component> <component id="44a8" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myCommandLineClient"> diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java index 38c099ba4136..cff64beea8b2 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java @@ -31,6 +31,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNStatus; +import org.tmatesoft.svn.core.wc2.SvnTarget; import java.io.File; import java.io.IOException; @@ -113,7 +114,8 @@ public class SvnContentRevision implements ContentRevision, MarkerVcsContentRevi if (lock.exists()) { throw new VcsException("Can not access file base revision contents: administrative area is locked"); } - return SvnUtil.getFileContents(myVcs, file.getPath(), false, myUseBaseRevision ? SVNRevision.BASE : myRevision, SVNRevision.UNDEFINED); + return SvnUtil.getFileContents(myVcs, SvnTarget.fromFile(file), myUseBaseRevision ? SVNRevision.BASE : myRevision, + SVNRevision.UNDEFINED); } @NotNull diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnDiffProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnDiffProvider.java index 2a8a00aa1ae6..813d41c48e24 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnDiffProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnDiffProvider.java @@ -17,6 +17,7 @@ package org.jetbrains.idea.svn; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.actions.VcsContextFactory; import com.intellij.openapi.vcs.changes.ContentRevision; import com.intellij.openapi.vcs.diff.DiffMixin; @@ -26,8 +27,10 @@ import com.intellij.openapi.vcs.history.VcsRevisionDescription; import com.intellij.openapi.vcs.history.VcsRevisionDescriptionImpl; import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn.commandLine.SvnCommandLineStatusClient; import org.jetbrains.idea.svn.history.LatestExistentSearcher; import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.wc.*; @@ -43,19 +46,12 @@ public class SvnDiffProvider implements DiffProvider, DiffMixin { } public VcsRevisionNumber getCurrentRevision(VirtualFile file) { - final SVNWCClient client = myVcs.createWCClient(); - try { - final SVNInfo svnInfo = client.doInfo(new File(file.getPresentableUrl()), SVNRevision.UNDEFINED); - if (svnInfo == null) return null; - if (SVNRevision.UNDEFINED.equals(svnInfo.getCommittedRevision()) && svnInfo.getCopyFromRevision() != null) { - return new SvnRevisionNumber(svnInfo.getCopyFromRevision()); - } - return new SvnRevisionNumber(svnInfo.getRevision()); - } - catch (SVNException e) { - LOG.debug(e); // most likely the file is unversioned - return null; + final SVNInfo svnInfo = myVcs.getInfo(new File(file.getPresentableUrl())); + if (svnInfo == null) return null; + if (SVNRevision.UNDEFINED.equals(svnInfo.getCommittedRevision()) && svnInfo.getCopyFromRevision() != null) { + return new SvnRevisionNumber(svnInfo.getCopyFromRevision()); } + return new SvnRevisionNumber(svnInfo.getRevision()); } @Override @@ -65,53 +61,36 @@ public class SvnDiffProvider implements DiffProvider, DiffMixin { } private VcsRevisionDescription getCurrentRevisionDescription(File path) { - final SVNWCClient client = myVcs.createWCClient(); - try { - final SVNInfo svnInfo = client.doInfo(path, SVNRevision.UNDEFINED); - - if (svnInfo.getCommittedRevision().equals(SVNRevision.UNDEFINED) && ! svnInfo.getCopyFromRevision().equals(SVNRevision.UNDEFINED) && + final SVNInfo svnInfo = myVcs.getInfo(path); + if (svnInfo == null) { + return null; + } + + if (svnInfo.getCommittedRevision().equals(SVNRevision.UNDEFINED) && !svnInfo.getCopyFromRevision().equals(SVNRevision.UNDEFINED) && svnInfo.getCopyFromURL() != null) { - SVNURL copyUrl = svnInfo.getCopyFromURL(); - String localPath = myVcs.getSvnFileUrlMapping().getLocalPath(copyUrl.toString()); - if (localPath != null) { - return getCurrentRevisionDescription(new File(localPath)); - } + SVNURL copyUrl = svnInfo.getCopyFromURL(); + String localPath = myVcs.getSvnFileUrlMapping().getLocalPath(copyUrl.toString()); + if (localPath != null) { + return getCurrentRevisionDescription(new File(localPath)); } - final String message = getProperties(client, path); + } + + try { + final String message = getCommitMessage(path); return new VcsRevisionDescriptionImpl(new SvnRevisionNumber(svnInfo.getCommittedRevision()), svnInfo.getCommittedDate(), svnInfo.getAuthor(), message); } - catch (SVNException e) { + catch (VcsException e) { LOG.debug(e); // most likely the file is unversioned return null; } } - private String getProperties(SVNWCClient client, File path) throws SVNException { - final String[] message = new String[1]; - client.doGetRevisionProperty(path, null, SVNRevision.BASE, new ISVNPropertyHandler() { - @Override - public void handleProperty(File path, SVNPropertyData property) throws SVNException { - handle(property); - } - - @Override - public void handleProperty(SVNURL url, SVNPropertyData property) throws SVNException { - handle(property); - } - - @Override - public void handleProperty(long revision, SVNPropertyData property) throws SVNException { - handle(property); - } + private String getCommitMessage(File path) throws VcsException { + SVNPropertyData property = + myVcs.getFactory(path).createPropertyClient().getProperty(path, COMMIT_MESSAGE, true, null, SVNRevision.BASE); - private void handle(SVNPropertyData data) { - if (COMMIT_MESSAGE.equals(data.getName())) { - message[0] = data.getValue().getString(); - } - } - }); - return message[0]; + return property != null ? SVNPropertyValue.getPropertyAsString(property.getValue()) : null; } private static ItemLatestState defaultResult() { @@ -135,18 +114,45 @@ public class SvnDiffProvider implements DiffProvider, DiffMixin { return SvnContentRevision.createBaseRevision(myVcs, filePath, svnRevision); } } + // not clear why we need it, with remote check.. - final SVNStatusClient client = myVcs.createStatusClient(); - try { - final SVNStatus svnStatus = client.doStatus(new File(selectedFile.getPresentableUrl()), false, false); - if (svnRevision.equals(svnStatus.getRevision())) { + SVNStatus svnStatus = getFileStatus(new File(selectedFile.getPresentableUrl()), false); + if (svnStatus != null && svnRevision.equals(svnStatus.getRevision())) { return SvnContentRevision.createBaseRevision(myVcs, filePath, svnRevision); - } + } + return SvnContentRevision.createRemote(myVcs, filePath, svnRevision); + } + + private SVNStatus getFileStatus(File file, boolean remote) { + WorkingCopyFormat format = myVcs.getWorkingCopyFormat(file); + + return WorkingCopyFormat.ONE_DOT_EIGHT.equals(format) ? getStatusCommandLine(file, remote) : getStatusWithSvnKit(file, remote); + } + + private SVNStatus getStatusWithSvnKit(File file, boolean remote) { + SVNStatus result = null; + + try { + result = myVcs.createStatusClient().doStatus(file, remote, false); } catch (SVNException e) { LOG.debug(e); // most likely the file is unversioned } - return SvnContentRevision.createRemote(myVcs, filePath, svnRevision); + + return result; + } + + private SVNStatus getStatusCommandLine(File file, boolean remote) { + SVNStatus result = null; + + try { + result = new SvnCommandLineStatusClient(myVcs.getProject()).doStatus(file, remote, false); + } + catch (SVNException e) { + LOG.debug(e); + } + + return result; } public ItemLatestState getLastRevision(FilePath filePath) { @@ -159,36 +165,29 @@ public class SvnDiffProvider implements DiffProvider, DiffMixin { } private ItemLatestState getLastRevision(final File file) { - final SVNStatusClient client = myVcs.createStatusClient(); - try { - final SVNStatus svnStatus = client.doStatus(file, true); - if (svnStatus == null || itemExists(svnStatus) && SVNRevision.UNDEFINED.equals(svnStatus.getRemoteRevision())) { - // IDEADEV-21785 (no idea why this can happen) - final SVNInfo info = myVcs.createWCClient().doInfo(file, SVNRevision.HEAD); - if (info == null || info.getURL() == null) { - LOG.info("No SVN status returned for " + file.getPath()); - return defaultResult(); - } - return createResult(info.getCommittedRevision(), true, false); + final SVNStatus svnStatus = getFileStatus(file, true); + if (svnStatus == null || itemExists(svnStatus) && SVNRevision.UNDEFINED.equals(svnStatus.getRemoteRevision())) { + // IDEADEV-21785 (no idea why this can happen) + final SVNInfo info = myVcs.getInfo(file, SVNRevision.HEAD); + if (info == null || info.getURL() == null) { + LOG.info("No SVN status returned for " + file.getPath()); + return defaultResult(); } - final boolean exists = itemExists(svnStatus); - if (! exists) { - // get really latest revision - final LatestExistentSearcher searcher = new LatestExistentSearcher(myVcs, svnStatus.getURL()); - final long revision = searcher.getDeletionRevision(); + return createResult(info.getCommittedRevision(), true, false); + } + final boolean exists = itemExists(svnStatus); + if (! exists) { + // get really latest revision + final LatestExistentSearcher searcher = new LatestExistentSearcher(myVcs, svnStatus.getURL()); + final long revision = searcher.getDeletionRevision(); - return createResult(SVNRevision.create(revision), exists, false); - } - final SVNRevision remoteRevision = svnStatus.getRemoteRevision(); - if (remoteRevision != null) { - return createResult(remoteRevision, exists, false); - } - return createResult(svnStatus.getRevision(), exists, false); + return createResult(SVNRevision.create(revision), exists, false); } - catch (SVNException e) { - LOG.debug(e); // most likely the file is unversioned - return defaultResult(); + final SVNRevision remoteRevision = svnStatus.getRemoteRevision(); + if (remoteRevision != null) { + return createResult(remoteRevision, exists, false); } + return createResult(svnStatus.getRevision(), exists, false); } private boolean itemExists(SVNStatus svnStatus) { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileSystemListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileSystemListener.java index 062c412f2336..da1adc60327c 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileSystemListener.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileSystemListener.java @@ -44,10 +44,7 @@ import com.intellij.util.containers.MultiMap; import com.intellij.vcsUtil.ActionWithTempFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.tmatesoft.svn.core.SVNErrorCode; -import org.tmatesoft.svn.core.SVNErrorMessage; -import org.tmatesoft.svn.core.SVNException; -import org.tmatesoft.svn.core.SVNNodeKind; +import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.wc.*; @@ -160,10 +157,10 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp } private class UUIDHelper { - private final SVNWCClient myWcClient; + private final SvnVcs myVcs; private UUIDHelper(final SvnVcs vcs) { - myWcClient = vcs.createWCClient(); + myVcs = vcs; } /** @@ -175,7 +172,7 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp final SVNInfo info1 = new RepeatSvnActionThroughBusy() { @Override protected void executeImpl() throws SVNException { - myT = myWcClient.doInfo(new File(dir.getPath()), SVNRevision.UNDEFINED); + myT = myVcs.getInfo(new File(dir.getPath())); } }.compute(); if (info1 == null || info1.getRepositoryUUID() == null) { @@ -244,10 +241,11 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp long srcTime = src.lastModified(); try { final boolean isUndo = isUndo(vcs); - final String list = isUndo ? null : SvnChangelistListener.getCurrentMapping(vcs.getProject(), src); + final String list = isUndo ? null : SvnChangelistListener.getCurrentMapping(vcs, src); - final boolean is17 = SvnUtil.is17CopyPart(src); - if (is17) { + WorkingCopyFormat format = vcs.getWorkingCopyFormat(src); + final boolean is17OrLater = WorkingCopyFormat.ONE_DOT_EIGHT.equals(format) || WorkingCopyFormat.ONE_DOT_SEVEN.equals(format); + if (is17OrLater) { SVNStatus srcStatus = getFileStatus(vcs, src); final File toDir = dst.getParentFile(); SVNStatus dstStatus = getFileStatus(vcs, toDir); @@ -283,29 +281,18 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp ourStatusesForUndoMove.add(SVNStatusType.STATUS_ADDED); } - private boolean for17move(SvnVcs vcs, final File src, final File dst, boolean undo, SVNStatus srcStatus) throws SVNException { + private boolean for17move(final SvnVcs vcs, final File src, final File dst, boolean undo, SVNStatus srcStatus) throws SVNException { if (srcStatus != null && srcStatus.getCopyFromURL() == null) { undo = false; } if (undo) { myUndoingMove = true; - final SVNWCClient wcClient = vcs.createWCClient(); - new RepeatSvnActionThroughBusy() { - @Override - protected void executeImpl() throws SVNException { - wcClient.doRevert(dst, true); - } - }.execute(); + createRevertAction(vcs, dst, true).execute(); copyUnversionedMembersOfDirectory(src, dst); if (srcStatus == null || SvnVcs.svnStatusIsUnversioned(srcStatus)) { FileUtil.delete(src); } else { - new RepeatSvnActionThroughBusy() { - @Override - protected void executeImpl() throws SVNException { - wcClient.doRevert(src, true); - } - }.execute(); + createRevertAction(vcs, src, true).execute(); } restoreFromUndoStorage(dst); } else { @@ -317,15 +304,9 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp copyFileOrDir(src, dst); } catch (IOException e) { - throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e); + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e), e); } - final SVNWCClient wcClient = vcs.createWCClient(); - new RepeatSvnActionThroughBusy() { - @Override - protected void executeImpl() throws SVNException { - wcClient.doDelete(src, true, false); - } - }.execute(); + createDeleteAction(vcs, src, true).execute(); return false; } moveFileWithSvn(vcs, src, dst); @@ -333,13 +314,16 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp return false; } - public static void moveFileWithSvn(SvnVcs vcs, File src, final File dst) throws SVNException { - final SVNCopyClient copyClient = vcs.createCopyClient(); - final SVNCopySource svnCopySource = new SVNCopySource(SVNRevision.UNDEFINED, SVNRevision.WORKING, src); + public static void moveFileWithSvn(final SvnVcs vcs, final File src, final File dst) throws SVNException { new RepeatSvnActionThroughBusy() { @Override protected void executeImpl() throws SVNException { - copyClient.doCopy(new SVNCopySource[]{svnCopySource}, dst, true, false, true); + try { + vcs.getFactory(src).createCopyMoveClient().copy(src, dst, false, true); + } + catch (VcsException e) { + wrapAndThrow(e); + } } }.execute(); } @@ -357,7 +341,7 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp copyFileOrDir(src, dst); } catch (IOException e) { - exc[0] = new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e); + exc[0] = new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e), e); return false; } } @@ -464,7 +448,7 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp * deleted: do nothing, return true (strange) */ public boolean delete(VirtualFile file) throws IOException { - SvnVcs vcs = getVCS(file); + final SvnVcs vcs = getVCS(file); if (vcs != null && SvnUtil.isAdminDirectory(file)) { return true; } @@ -476,12 +460,8 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp if (! SvnUtil.isSvnVersioned(vcs.getProject(), ioFile.getParentFile())) { return false; } - try { - if (SVNWCUtil.isWorkingCopyRoot(ioFile)) { - return false; - } - } catch (SVNException e) { - // + if (SvnUtil.isWorkingCopyRoot(ioFile)) { + return false; } SVNStatus status = getFileStatus(vcs, ioFile); @@ -507,13 +487,7 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp } if (SvnVcs.svnStatusIs(status, SVNStatusType.STATUS_ADDED)) { try { - final SVNWCClient wcClient = vcs.createWCClient(); - new RepeatSvnActionThroughBusy() { - @Override - protected void executeImpl() throws SVNException { - wcClient.doRevert(ioFile, false); - } - }.execute(); + createRevertAction(vcs, ioFile, false).execute(); } catch (SVNException e) { // ignore @@ -529,6 +503,41 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp } } + @NotNull + private RepeatSvnActionThroughBusy createRevertAction(@NotNull final SvnVcs vcs, @NotNull final File file, final boolean recursive) { + return new RepeatSvnActionThroughBusy() { + @Override + protected void executeImpl() throws SVNException { + try { + vcs.getFactory(file).createRevertClient().revert(new File[]{file}, SVNDepth.fromRecurse(recursive), null); + } + catch (VcsException e) { + wrapAndThrow(e); + } + } + }; + } + + @NotNull + private RepeatSvnActionThroughBusy createDeleteAction(@NotNull final SvnVcs vcs, @NotNull final File file, final boolean force) { + return new RepeatSvnActionThroughBusy() { + @Override + protected void executeImpl() throws SVNException { + try { + vcs.getFactory(file).createDeleteClient().delete(file, force); + } + catch (VcsException e) { + wrapAndThrow(e); + } + } + }; + } + + private static void wrapAndThrow(VcsException e) throws SVNException { + // TODO: probably we should wrap into new exception only if e.getCause is not SVNException + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.FS_GENERAL, e), e); + } + private boolean isAboveSourceOfCopyOrMove(final Project p, File ioFile) { for (MovedFileInfo file : myMovedFiles) { if (FileUtil.isAncestor(ioFile, file.mySrc, false)) return true; @@ -583,7 +592,6 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp if (! SvnUtil.isSvnVersioned(vcs.getProject(), ioDir) && ! pendingAdd) { return false; } - final SVNWCClient wcClient = vcs.createWCClient(); final File targetFile = new File(ioDir, name); SVNStatus status = getFileStatus(vcs, targetFile); @@ -603,12 +611,7 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp } try { if (isUndo(vcs)) { - new RepeatSvnActionThroughBusy() { - @Override - protected void executeImpl() throws SVNException { - wcClient.doRevert(targetFile, false); - } - }.execute(); + createRevertAction(vcs, targetFile, false).execute(); return true; } myAddedFiles.putValue(vcs.getProject(), new AddedFileInfo(dir, name, null, recursive)); @@ -759,8 +762,6 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp return new Runnable() { @Override public void run() { - final SVNWCClient wcClient = vcs.createWCClient(); - final SVNCopyClient copyClient = vcs.createCopyClient(); for(VirtualFile file: filesToProcess) { final File ioFile = new File(file.getPath()); try { @@ -771,11 +772,15 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp protected void executeInternal() throws VcsException { try { // not recursive - final SVNCopySource[] copySource = {new SVNCopySource(SVNRevision.WORKING, SVNRevision.WORKING, copyFrom)}; new RepeatSvnActionThroughBusy() { @Override protected void executeImpl() throws SVNException { - copyClient.doCopy(copySource, ioFile, false, true, true); + try { + vcs.getFactory(copyFrom).createCopyMoveClient().copy(copyFrom, ioFile, true, false); + } + catch (VcsException e) { + wrapAndThrow(e); + } } }.execute(); } @@ -793,7 +798,12 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp new RepeatSvnActionThroughBusy() { @Override protected void executeImpl() throws SVNException { - wcClient.doAdd(ioFile, true, false, false, true); + try { + vcs.getFactory(ioFile).createAddClient().add(ioFile, null, false, false, true, null); + } + catch (VcsException e) { + wrapAndThrow(e); + } } }.execute(); } @@ -915,17 +925,11 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp final List<VcsException> exceptions) { return new Runnable() { public void run() { - final SVNWCClient wcClient = vcs.createWCClient(); for(FilePath file: filesToProcess) { VirtualFile vFile = file.getVirtualFile(); // for deleted directories final File ioFile = new File(file.getPath()); try { - new RepeatSvnActionThroughBusy() { - @Override - protected void executeImpl() throws SVNException { - wcClient.doDelete(ioFile, true, false); - } - }.execute(); + createDeleteAction(vcs, ioFile, true).execute(); if (vFile != null && vFile.isValid() && vFile.isDirectory()) { vFile.refresh(true, true); VcsDirtyScopeManager.getInstance(project).dirDirtyRecursively(vFile); @@ -978,18 +982,15 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp private void fillDeletedFiles(Project project, List<Pair<FilePath, WorkingCopyFormat>> deletedFiles, Collection<FilePath> deleteAnyway) throws SVNException { final SvnVcs vcs = SvnVcs.getInstance(project); - final SVNStatusClient sc = vcs.createStatusClient(); final Collection<File> files = myDeletedFiles.remove(project); for (final File file : files) { - boolean isAdded = false; - final SVNStatus status; - status = new RepeatSvnActionThroughBusy() { - @Override - protected void executeImpl() throws SVNException { - myT = sc.doStatus(file, false); - } - }.compute(); - isAdded = SVNStatusType.STATUS_ADDED.equals(status.getNodeStatus()); + final SVNStatus status = new RepeatSvnActionThroughBusy() { + @Override + protected void executeImpl() throws SVNException { + myT = vcs.getFactory(file).createStatusClient().doStatus(file, false); + } + }.compute(); + boolean isAdded = SVNStatusType.STATUS_ADDED.equals(status.getNodeStatus()); final FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(file); if (isAdded) { deleteAnyway.add(filePath); @@ -1034,18 +1035,12 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp } @Nullable - private static SVNStatus getFileStatus(SvnVcs vcs, File file) { - SVNStatusClient stClient = vcs.createStatusClient(); - return getFileStatus(file, stClient); - } - - @Nullable - private static SVNStatus getFileStatus(final File file, final SVNStatusClient stClient) { + private static SVNStatus getFileStatus(@NotNull final SvnVcs vcs, @NotNull final File file) { try { return new RepeatSvnActionThroughBusy() { @Override protected void executeImpl() throws SVNException { - myT = stClient.doStatus(file, false); + myT = vcs.getFactory(file).createStatusClient().doStatus(file, false); } }.compute(); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileUrlMappingImpl.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileUrlMappingImpl.java index 46545c43a4cc..68e7c07848c2 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileUrlMappingImpl.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileUrlMappingImpl.java @@ -312,8 +312,8 @@ public class SvnFileUrlMappingImpl implements SvnFileUrlMapping, PersistentState LOG.info("Error: cannot find repository URL for versioned folder: " + foundRoot.getFile().getPath()); } else { myRepositoryRoots.register(repoRoot); - myTopRoots.add(new RootUrlInfo(repoRoot, foundRoot.getInfo().getURL(), - SvnFormatSelector.getWorkingCopyFormat(new File(foundRoot.getFile().getPath())), foundRoot.getFile(), vcsRoot)); + myTopRoots.add(new RootUrlInfo(repoRoot, foundRoot.getInfo().getURL(), SvnFormatSelector.findRootAndGetFormat( + new File(foundRoot.getFile().getPath())), foundRoot.getFile(), vcsRoot)); } } } @@ -541,8 +541,9 @@ public class SvnFileUrlMappingImpl implements SvnFileUrlMapping, PersistentState final SVNInfo svnInfo = vcs.getInfo(copyRoot); if ((svnInfo == null) || (svnInfo.getRepositoryRootURL() == null)) continue; - final RootUrlInfo info = new RootUrlInfo(svnInfo.getRepositoryRootURL(), svnInfo.getURL(), - SvnFormatSelector.getWorkingCopyFormat(svnInfo.getFile()), copyRoot, vcsRoot); + final RootUrlInfo info = + new RootUrlInfo(svnInfo.getRepositoryRootURL(), svnInfo.getURL(), SvnFormatSelector.findRootAndGetFormat(svnInfo.getFile()), + copyRoot, vcsRoot); mapping.add(info); } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFormatSelector.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFormatSelector.java index 94d21dbbbd9d..77783badc2f8 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFormatSelector.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFormatSelector.java @@ -16,6 +16,7 @@ package org.jetbrains.idea.svn; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.util.WaitForProgressToShow; @@ -37,6 +38,9 @@ import java.util.Collections; import java.util.Iterator; public class SvnFormatSelector implements ISVNAdminAreaFactorySelector { + + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnFormatSelector"); + public Collection getEnabledFactories(File path, Collection factories, boolean writeAccess) throws SVNException { if (ApplicationManager.getApplication().isUnitTestMode()) { return factories; @@ -145,7 +149,19 @@ public class SvnFormatSelector implements ISVNAdminAreaFactorySelector { return newMode[0]; } + public static WorkingCopyFormat findRootAndGetFormat(final File path) { + File root = SvnUtil.getWorkingCopyRootNew(path); + + return root != null ? getWorkingCopyFormat(root) : WorkingCopyFormat.UNKNOWN; + } + public static WorkingCopyFormat getWorkingCopyFormat(final File path) { + WorkingCopyFormat format = SvnUtil.getFormat(path); + + return WorkingCopyFormat.UNKNOWN.equals(format) ? detectWithSvnKit(path) : format; + } + + private static WorkingCopyFormat detectWithSvnKit(File path) { try { final SvnWcGeneration svnWcGeneration = SvnOperationFactory.detectWcGeneration(path, true); if (SvnWcGeneration.V17.equals(svnWcGeneration)) return WorkingCopyFormat.ONE_DOT_SEVEN; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnProxies.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnProxies.java index 97b87efaa7f9..fcac3966c2d6 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnProxies.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnProxies.java @@ -29,6 +29,7 @@ import java.util.concurrent.atomic.AtomicReference; * Date: 10/19/12 * Time: 4:22 PM */ +// TODO: Likely to be removed (together with SvnAbstractWriteOperationLocks). public class SvnProxies { private final SvnAbstractWriteOperationLocks myLocks; private final AtomicReference<LearningProxy<SVNChangelistClientI, SVNException>> myChangelist; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java index 4a49dc16a153..8b518199f5be 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java @@ -47,13 +47,15 @@ import java.util.LinkedList; public class SvnRecursiveStatusWalker { private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnRecursiveStatusWalker"); private final StatusWalkerPartner myPartner; + private final SvnVcs myVcs; private final Project myProject; private final StatusReceiver myReceiver; private final LinkedList<MyItem> myQueue; private final MyHandler myHandler; - public SvnRecursiveStatusWalker(final Project project, final StatusReceiver receiver, final StatusWalkerPartner partner) { - myProject = project; + public SvnRecursiveStatusWalker(final SvnVcs vcs, final StatusReceiver receiver, final StatusWalkerPartner partner) { + myVcs = vcs; + myProject = vcs.getProject(); myReceiver = receiver; myPartner = partner; myQueue = new LinkedList<MyItem>(); @@ -61,7 +63,7 @@ public class SvnRecursiveStatusWalker { } public void go(final FilePath rootPath, final SVNDepth depth) throws SVNException { - final MyItem root = new MyItem(myProject, rootPath, depth, myPartner.createStatusClient(), false); + final MyItem root = new MyItem(myVcs, rootPath, depth, myPartner.createStatusClient(), false); myQueue.add(root); while (! myQueue.isEmpty()) { @@ -83,7 +85,7 @@ public class SvnRecursiveStatusWalker { } } else { try { - final SVNStatus status = item.getClient().doStatus(ioFile, false, false); + final SVNStatus status = item.getClient(ioFile).doStatus(ioFile, false, false); myReceiver.process(path, status); } catch (SVNException e) { handleStatusException(item, path, e); @@ -115,18 +117,20 @@ public class SvnRecursiveStatusWalker { private final Project myProject; private final FilePath myPath; private final SVNDepth myDepth; - private final SVNStatusClient myClient; private final SvnStatusClientI mySvnClient; + private final SvnStatusClientI myCommandLineClient; private final boolean myIsInnerCopyRoot; private final SvnConfiguration myConfiguration17; + private final SvnVcs myVcs; - private MyItem(Project project, FilePath path, SVNDepth depth, SVNStatusClient client, boolean isInnerCopyRoot) { - myProject = project; + private MyItem(SvnVcs vcs, FilePath path, SVNDepth depth, SVNStatusClient client, boolean isInnerCopyRoot) { + myVcs = vcs; + myProject = vcs.getProject(); myConfiguration17 = SvnConfiguration.getInstance(myProject); myPath = path; myDepth = depth; - myClient = client; mySvnClient = new SvnkitSvnStatusClient(client); + myCommandLineClient = new SvnCommandLineStatusClient(myProject); myIsInnerCopyRoot = isInnerCopyRoot; } @@ -138,20 +142,20 @@ public class SvnRecursiveStatusWalker { return myDepth; } - public SvnStatusClientI getClient() { - return mySvnClient; - } - public SvnStatusClientI getClient(final File file) { - if (! SVNDepth.INFINITY.equals(myDepth)) { - return mySvnClient; + WorkingCopyFormat format = myVcs.getWorkingCopyFormat(file); + + if (format == WorkingCopyFormat.ONE_DOT_EIGHT) { + return myCommandLineClient; } + // check format if (CheckJavaHL.isPresent() && SvnConfiguration.UseAcceleration.javaHL.equals(myConfiguration17.myUseAcceleration) && Svn17Detector.is17(myProject, file)) { return new JavaHLSvnStatusClient(myProject); - } else if (SvnConfiguration.UseAcceleration.commandLine.equals(myConfiguration17.myUseAcceleration) && Svn17Detector.is17(myProject, file)) { - return new SvnCommandLineStatusClient(myProject); + } else if (SvnConfiguration.UseAcceleration.commandLine.equals(myConfiguration17.myUseAcceleration)) { + // apply command line disregarding working copy format + return myCommandLineClient; } return mySvnClient; } @@ -186,7 +190,7 @@ public class SvnRecursiveStatusWalker { return true; } if (file.isDirectory() && new File(file, SVNFileUtil.getAdminDirectoryName()).exists()) { - final MyItem childItem = new MyItem(myProject, path, newDepth, myPartner.createStatusClient(), true); + final MyItem childItem = new MyItem(myVcs, path, newDepth, myPartner.createStatusClient(), true); myQueue.add(childItem); } else if (vf != null) { myReceiver.processUnversioned(vf); @@ -228,12 +232,13 @@ public class SvnRecursiveStatusWalker { } public void checkIfCopyRootWasReported(@Nullable final SVNStatus ioFileStatus, final File ioFile) { - if (! myMetCurrentItem && FileUtil.filesEqual(ioFile, myCurrentItem.getPath().getIOFile())) { + File itemFile = myCurrentItem.getPath().getIOFile(); + if (! myMetCurrentItem && FileUtil.filesEqual(ioFile, itemFile)) { myMetCurrentItem = true; SVNStatus statusInner; try { statusInner = ioFileStatus != null ? ioFileStatus : - myCurrentItem.getClient().doStatus(myCurrentItem.getPath().getIOFile(), false); + myCurrentItem.getClient(itemFile).doStatus(itemFile, false); } catch (SVNException e) { LOG.info(e); @@ -294,7 +299,7 @@ public class SvnRecursiveStatusWalker { //myReceiver.processUnversioned(vFile); //processRecursively(vFile, myCurrentItem.getDepth()); } else { - final MyItem childItem = new MyItem(myProject, new FilePathImpl(vFile), SVNDepth.INFINITY, + final MyItem childItem = new MyItem(myVcs, new FilePathImpl(vFile), SVNDepth.INFINITY, myPartner.createStatusClient(), true); myQueue.add(childItem); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnTestWriteOperationLocks.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnTestWriteOperationLocks.java index 853e995ca219..efea2f93de08 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnTestWriteOperationLocks.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnTestWriteOperationLocks.java @@ -25,6 +25,7 @@ import java.io.File; * Date: 10/23/12 * Time: 2:31 PM */ +// TODO: Used only in SvnLockingTest which is not required anymore. Likely to be removed. public class SvnTestWriteOperationLocks extends SvnAbstractWriteOperationLocks { private final WorkingCopy myWorkingCopy; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnUtil.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnUtil.java index 0c049ac4671d..7cd6bdd91d3f 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnUtil.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnUtil.java @@ -30,21 +30,19 @@ import com.intellij.openapi.vcs.AbstractVcsHelper; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.ChangesUtil; -import com.intellij.openapi.vcs.impl.ContentRevisionCache; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VfsUtilCore; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.*; import com.intellij.openapi.wm.impl.status.StatusBarUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.Convertor; import com.intellij.util.containers.MultiMap; -import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.ClientFactory; import org.jetbrains.idea.svn.branchConfig.SvnBranchConfigurationNew; import org.jetbrains.idea.svn.dialogs.LockDialog; +import org.tmatesoft.sqljet.core.SqlJetException; +import org.tmatesoft.sqljet.core.table.SqlJetDb; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; @@ -53,14 +51,14 @@ import org.tmatesoft.svn.core.io.SVNCapability; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.*; import org.tmatesoft.svn.core.wc2.SvnOperationFactory; +import org.tmatesoft.svn.core.wc2.SvnTarget; -import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.IOException; -import java.io.OutputStream; import java.util.*; public class SvnUtil { + // TODO: ASP.NET hack behavior should be supported - http://svn.apache.org/repos/asf/subversion/trunk/notes/asp-dot-net-hack.txt + // TODO: Remember this when moving out SVNKit classes. @NonNls public static final String SVN_ADMIN_DIR_NAME = SVNFileUtil.getAdminDirectoryName(); @NonNls public static final String ENTRIES_FILE_NAME = "entries"; @NonNls public static final String WC_DB_FILE_NAME = "wc.db"; @@ -71,13 +69,9 @@ public class SvnUtil { private SvnUtil() { } public static boolean isSvnVersioned(final Project project, File parent) { - try { - final SVNInfo info = SvnVcs.getInstance(project).createWCClient().doInfo(parent, SVNRevision.UNDEFINED); - return info != null; - } - catch (SVNException e) { - return false; - } + final SVNInfo info = SvnVcs.getInstance(project).getInfo(parent); + + return info != null; } public static Collection<VirtualFile> crawlWCRoots(final Project project, File path, SvnWCRootCrawler callback, ProgressIndicator progress) { @@ -123,15 +117,8 @@ public class SvnUtil { @Nullable public static String getExactLocation(final SvnVcs vcs, File path) { - try { - SVNWCClient wcClient = vcs.createWCClient(); - SVNInfo info = wcClient.doInfo(path, SVNRevision.UNDEFINED); - if (info != null && info.getURL() != null) { - return info.getURL().toString(); - } - } - catch (SVNException ignored) { } - return null; + SVNInfo info = vcs.getInfo(path); + return info != null && info.getURL() != null ? info.getURL().toString() : null; } public static Map<String, File> getLocationInfoForModule(final SvnVcs vcs, File path, ProgressIndicator progress) { @@ -288,7 +275,9 @@ public class SvnUtil { } public static String formatRepresentation(final WorkingCopyFormat format) { - if (WorkingCopyFormat.ONE_DOT_SEVEN.equals(format)) { + if (WorkingCopyFormat.ONE_DOT_EIGHT.equals(format)) { + return SvnBundle.message("dialog.show.svn.map.table.version18.text"); + } else if (WorkingCopyFormat.ONE_DOT_SEVEN.equals(format)) { return SvnBundle.message("dialog.show.svn.map.table.version17.text"); } else if (WorkingCopyFormat.ONE_DOT_SIX.equals(format)) { return SvnBundle.message("dialog.show.svn.map.table.version16.text"); @@ -352,6 +341,54 @@ public class SvnUtil { return result; } + /** + * Gets working copy internal format. Works for 1.7 and 1.8. + * + * @param path + * @return + */ + public static WorkingCopyFormat getFormat(final File path) { + int format = 0; + File dbFile = resolveDatabase(path); + + if (dbFile != null) { + SqlJetDb db = null; + try { + db = SqlJetDb.open(dbFile, false); + format = db.getOptions().getUserVersion(); + } + catch (SqlJetException e) { + LOG.error(e); + } finally { + if (db != null) { + try { + db.close(); + } + catch (SqlJetException e) { + LOG.error(e); + } + } + } + } + + return WorkingCopyFormat.getInstance(format); + } + + private static File resolveDatabase(final File path) { + File dbFile = getWcDb(path); + File result = null; + + try { + if (dbFile.exists() && dbFile.isFile()) { + result = dbFile; + } + } catch (SecurityException e) { + LOG.error("Failed to access working copy database", e); + } + + return result; + } + private static class LocationsCrawler implements SvnWCRootCrawler { private final SvnVcs myVcs; private final Map<String, File> myLocations; @@ -371,15 +408,9 @@ public class SvnUtil { oldText = progress.getText(); progress.setText(SvnBundle.message("progress.text.discovering.location", root.getAbsolutePath())); } - try { - SVNWCClient wcClient = myVcs.createWCClient(); - SVNInfo info = wcClient.doInfo(root, SVNRevision.UNDEFINED); - if (info != null && info.getURL() != null) { - myLocations.put(info.getURL().toString(), info.getFile()); - } - } - catch (SVNException e) { - // + SVNInfo info = myVcs.getInfo(root); + if (info != null && info.getURL() != null) { + myLocations.put(info.getURL().toString(), info.getFile()); } if (progress != null) { progress.setText(oldText); @@ -389,20 +420,15 @@ public class SvnUtil { @Nullable public static String getRepositoryUUID(final SvnVcs vcs, final File file) { - final SVNWCClient client = vcs.createWCClient(); - try { - final SVNInfo info = client.doInfo(file, SVNRevision.UNDEFINED); - return (info == null) ? null : info.getRepositoryUUID(); - } catch (SVNException e) { - return null; - } + final SVNInfo info = vcs.getInfo(file); + return info != null ? info.getRepositoryUUID() : null; } @Nullable public static String getRepositoryUUID(final SvnVcs vcs, final SVNURL url) { - final SVNWCClient client = vcs.createWCClient(); try { - final SVNInfo info = client.doInfo(url, SVNRevision.UNDEFINED, SVNRevision.UNDEFINED); + final SVNInfo info = vcs.getInfo(url, SVNRevision.UNDEFINED); + return (info == null) ? null : info.getRepositoryUUID(); } catch (SVNException e) { return null; @@ -411,19 +437,14 @@ public class SvnUtil { @Nullable public static SVNURL getRepositoryRoot(final SvnVcs vcs, final File file) { - final SVNWCClient client = vcs.createWCClient(); - try { - final SVNInfo info = client.doInfo(file, SVNRevision.UNDEFINED); - return (info == null) ? null : info.getRepositoryRootURL(); - } catch (SVNException e) { - return null; - } + final SVNInfo info = vcs.getInfo(file); + return info != null ? info.getRepositoryRootURL() : null; } @Nullable public static SVNURL getRepositoryRoot(final SvnVcs vcs, final String url) { try { - return getRepositoryRoot(vcs, SVNURL.parseURIEncoded(url), true); + return getRepositoryRoot(vcs, SVNURL.parseURIEncoded(url)); } catch (SVNException e) { return null; @@ -431,18 +452,14 @@ public class SvnUtil { } @Nullable - public static SVNURL getRepositoryRoot(final SvnVcs vcs, final SVNURL url, boolean allowRemote) throws SVNException { - final SVNWCClient client = vcs.createWCClient(); - SVNInfo info = client.doInfo(url, SVNRevision.UNDEFINED, SVNRevision.HEAD); + public static SVNURL getRepositoryRoot(final SvnVcs vcs, final SVNURL url) throws SVNException { + SVNInfo info = vcs.getInfo(url, SVNRevision.HEAD); + return (info == null) ? null : info.getRepositoryRootURL(); } public static boolean isWorkingCopyRoot(final File file) { - try { - return SVNWCUtil.isWorkingCopyRoot(file); - } catch (SVNException e) { - return false; - } + return FileUtil.filesEqual(file, getWorkingCopyRootNew(file)); } @Nullable @@ -548,17 +565,9 @@ public class SvnUtil { } public static SVNDepth getDepth(final SvnVcs vcs, final File file) { - final SVNWCClient client = vcs.createWCClient(); - try { - final SVNInfo svnInfo = client.doInfo(file, SVNRevision.UNDEFINED); - if (svnInfo != null) { - return svnInfo.getDepth(); - } - } - catch (SVNException e) { - // - } - return SVNDepth.UNKNOWN; + SVNInfo info = vcs.getInfo(file); + + return info != null && info.getDepth() != null ? info.getDepth() : SVNDepth.UNKNOWN; } public static boolean seemsLikeVersionedDir(final VirtualFile file) { @@ -590,21 +599,17 @@ public class SvnUtil { } public static SVNURL getCommittedURL(final SvnVcs vcs, final File file) { - final File root = getWorkingCopyRoot(file); - if (root == null) return null; - return getUrl(vcs, root); + final File root = getWorkingCopyRootNew(file); + + return root == null ? null : getUrl(vcs, root); } @Nullable public static SVNURL getUrl(final SvnVcs vcs, final File file) { - try { - final SVNInfo info = vcs.createWCClient().doInfo(file, SVNRevision.UNDEFINED); - return info == null ? null : info.getURL(); // todo for moved items? - } - catch (SVNException e) { - LOG.debug(e); - return null; - } + // todo for moved items? + final SVNInfo info = vcs.getInfo(file); + + return info == null ? null : info.getURL(); } public static boolean doesRepositorySupportMergeInfo(final SvnVcs vcs, final SVNURL url) { @@ -648,17 +653,9 @@ public class SvnUtil { @Nullable public static File getWcCopyRootIf17(final File file, @Nullable final File upperBound) { - File current = file; - boolean wcDbFound = false; - while (current != null) { - File wcDb; - if ((wcDb = getWcDb(current)).exists() && ! wcDb.isDirectory()) { - wcDbFound = true; - break; - } - current = current.getParentFile(); - } - if (! wcDbFound) return null; + File current = getParentWithDb(file); + if (current == null) return null; + while (current != null) { try { final SvnWcGeneration svnWcGeneration = SvnOperationFactory.detectWcGeneration(current, false); @@ -674,6 +671,40 @@ public class SvnUtil { return null; } + /** + * Utility method that deals also with 1.8 working copies. + * TODO: Should be renamed when all parts updated for 1.8. + * + * @param file + * @return + */ + @Nullable + public static File getWorkingCopyRootNew(final File file) { + File current = getParentWithDb(file); + if (current == null) return getWorkingCopyRoot(file); + + WorkingCopyFormat format = getFormat(current); + + return WorkingCopyFormat.ONE_DOT_EIGHT.equals(format) || WorkingCopyFormat.ONE_DOT_SEVEN.equals(format) + ? current + : getWorkingCopyRoot(file); + } + + private static File getParentWithDb(File file) { + File current = file; + boolean wcDbFound = false; + while (current != null) { + File wcDb; + if ((wcDb = getWcDb(current)).exists() && ! wcDb.isDirectory()) { + wcDbFound = true; + break; + } + current = current.getParentFile(); + } + if (! wcDbFound) return null; + return current; + } + public static boolean is17CopyPart(final File file) { try { return SvnWcGeneration.V17.equals(SvnOperationFactory.detectWcGeneration(file, true)); @@ -703,44 +734,24 @@ public class SvnUtil { return result; } - public static byte[] getFileContents(final SvnVcs vcs, final String path, final boolean isUrl, final SVNRevision revision, - final SVNRevision pegRevision) + public static byte[] getFileContents(@NotNull final SvnVcs vcs, + @NotNull final SvnTarget target, + @Nullable final SVNRevision revision, + @Nullable final SVNRevision pegRevision) throws VcsException { - final int maxSize = VcsUtil.getMaxVcsLoadedFileSize(); - ByteArrayOutputStream buffer = new ByteArrayOutputStream() { - @Override - public synchronized void write(int b) { - if (size() > maxSize) throw new FileTooBigRuntimeException(); - super.write(b); - } + ClientFactory factory = target.isFile() ? vcs.getFactory(target.getFile()) : vcs.getFactory(); - @Override - public synchronized void write(byte[] b, int off, int len) { - if (size() > maxSize) throw new FileTooBigRuntimeException(); - super.write(b, off, len); - } + return factory.createContentClient().getContent(target, revision, pegRevision); + } - @Override - public synchronized void writeTo(OutputStream out) throws IOException { - if (size() > maxSize) throw new FileTooBigRuntimeException(); - super.writeTo(out); - } - }; - SVNWCClient wcClient = vcs.createWCClient(); + public static SVNURL parseUrl(@NotNull String url) { try { - if (isUrl) { - wcClient.doGetFileContents(SVNURL.parseURIEncoded(path), pegRevision, revision, true, buffer); - } else { - wcClient.doGetFileContents(new File(path), pegRevision, revision, true, buffer); - } - ContentRevisionCache.checkContentsSize(path, buffer.size()); - } catch (FileTooBigRuntimeException e) { - ContentRevisionCache.checkContentsSize(path, buffer.size()); - } catch (SVNException e) { - throw new VcsException(e); + return SVNURL.parseURIEncoded(url); + } + catch (SVNException e) { + IllegalArgumentException runtimeException = new IllegalArgumentException(); + runtimeException.initCause(e); + throw runtimeException; } - return buffer.toByteArray(); } - - private static class FileTooBigRuntimeException extends RuntimeException {} } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java index 748fc86c694c..1a5a0f8b8af7 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java @@ -13,8 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - package org.jetbrains.idea.svn; import com.intellij.ide.FrameStateListener; @@ -68,8 +66,12 @@ import org.jetbrains.idea.svn.actions.CleanupWorker; import org.jetbrains.idea.svn.actions.ShowPropertiesDiffWithLocalAction; import org.jetbrains.idea.svn.actions.SvnMergeProvider; import org.jetbrains.idea.svn.annotate.SvnAnnotationProvider; +import org.jetbrains.idea.svn.api.ClientFactory; +import org.jetbrains.idea.svn.api.CmdClientFactory; +import org.jetbrains.idea.svn.api.SvnKitClientFactory; import org.jetbrains.idea.svn.checkin.SvnCheckinEnvironment; import org.jetbrains.idea.svn.checkout.SvnCheckoutProvider; +import org.jetbrains.idea.svn.commandLine.SvnCommandLineInfoClient; import org.jetbrains.idea.svn.commandLine.SvnExecutableChecker; import org.jetbrains.idea.svn.dialogs.SvnBranchPointsCalculator; import org.jetbrains.idea.svn.dialogs.WCInfo; @@ -79,6 +81,7 @@ import org.jetbrains.idea.svn.history.SvnCommittedChangesProvider; import org.jetbrains.idea.svn.history.SvnHistoryProvider; import org.jetbrains.idea.svn.lowLevel.PrimitivePool; import org.jetbrains.idea.svn.networking.SSLProtocolExceptionParser; +import org.jetbrains.idea.svn.portable.SvnWcClientI; import org.jetbrains.idea.svn.rollback.SvnRollbackEnvironment; import org.jetbrains.idea.svn.update.SvnIntegrateEnvironment; import org.jetbrains.idea.svn.update.SvnUpdateEnvironment; @@ -195,9 +198,10 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { }; private SvnCheckoutProvider myCheckoutProvider; - public void checkCommandLineVersion() { - myChecker.checkExecutableAndNotifyIfNeeded(); - } + private ClientFactory cmdClientFactory; + private ClientFactory svnKitClientFactory; + + private final boolean myLogExceptions; static { System.setProperty("svnkit.log.native.calls", "true"); @@ -235,8 +239,8 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { public SvnVcs(final Project project, MessageBus bus, SvnConfiguration svnConfiguration, final SvnLoadedBrachesStorage storage) { super(project, VCS_NAME); + myLoadedBranchesStorage = storage; - LOG.debug("ct"); myRootsToWorkingCopies = new RootsToWorkingCopies(this); myConfiguration = svnConfiguration; myAuthNotifier = new SvnAuthenticationNotifier(this); @@ -284,6 +288,9 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { // remove used some time before old notification group ids correctNotificationIds(); myChecker = new SvnExecutableChecker(myProject); + + Application app = ApplicationManager.getApplication(); + myLogExceptions = app != null && (app.isInternal() || app.isUnitTestMode()); } private void correctNotificationIds() { @@ -348,6 +355,10 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { }); } + public void checkCommandLineVersion() { + myChecker.checkExecutableAndNotifyIfNeeded(); + } + public void invokeRefreshSvnRoots() { if (REFRESH_LOG.isDebugEnabled()) { REFRESH_LOG.debug("refresh: ", new Throwable()); @@ -481,6 +492,9 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { myChecker.checkExecutableAndNotifyIfNeeded(); } + cmdClientFactory = new CmdClientFactory(this); + svnKitClientFactory = new SvnKitClientFactory(this); + // do one time after project loaded StartupManager.getInstance(myProject).runWhenProjectIsInitialized(new DumbAwareRunnable() { @Override @@ -911,24 +925,127 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { return new File(file, pathToDirProps); } + /** + * Provides info either with command line or SvnKit based on project settings. + * Call this method only if failed to detect working copy format by any other means. + * + * @param file + * @return + */ + private SVNInfo runInfoCommand(@NotNull final File file) { + SVNInfo result = null; + + try { + result = SvnConfiguration.UseAcceleration.commandLine.equals(myConfiguration.myUseAcceleration) + ? getInfoCommandLine(file, SVNRevision.UNDEFINED) + : getInfoSvnKit(file); + } + catch (SVNException e) { + handleInfoException(e); + } + + return result; + } + + public SVNInfo getInfo(@NotNull SVNURL url, + SVNRevision pegRevision, + SVNRevision revision, + ISVNAuthenticationManager manager) throws SVNException { + if (SvnConfiguration.UseAcceleration.commandLine.equals(myConfiguration.myUseAcceleration)) { + return createInfoClient().doInfo(url, pegRevision, revision); + } else { + return (manager != null ? createWCClient(manager) : createWCClient()).doInfo(url, pegRevision, revision); + } + } + + public SVNInfo getInfo(@NotNull SVNURL url, SVNRevision revision) throws SVNException { + return getInfo(url, SVNRevision.UNDEFINED, revision, null); + } + @Nullable - public SVNInfo getInfo(final VirtualFile file) { + public SVNInfo getInfo(@NotNull final VirtualFile file) { final File ioFile = new File(file.getPath()); return getInfo(ioFile); } - public SVNInfo getInfo(File ioFile) { + @Nullable + public SVNInfo getInfo(@NotNull String path) { + return getInfo(new File(path)); + } + + @Nullable + public SVNInfo getInfo(@NotNull File ioFile) { + WorkingCopyFormat format = getWorkingCopyFormat(ioFile); + SVNInfo result = null; + try { - SVNWCClient wcClient = createWCClient(); - SVNInfo info = wcClient.doInfo(ioFile, SVNRevision.UNDEFINED); - if (info == null || info.getRepositoryRootURL() == null) { - info = wcClient.doInfo(ioFile, SVNRevision.HEAD); - } - return info; + result = format == WorkingCopyFormat.ONE_DOT_EIGHT ? getInfoCommandLine(ioFile, SVNRevision.UNDEFINED) : runInfoCommand(ioFile); } catch (SVNException e) { - return null; + handleInfoException(e); } + + return result; + } + + @Nullable + public SVNInfo getInfo(@NotNull File ioFile, @NotNull SVNRevision revision) { + WorkingCopyFormat format = getWorkingCopyFormat(ioFile); + SVNInfo result = null; + + try { + result = format == WorkingCopyFormat.ONE_DOT_EIGHT ? getInfoCommandLine(ioFile, revision) : getInfoSvnKit(ioFile, revision); + } + catch (SVNException e) { + handleInfoException(e); + } + + return result; + } + + private void handleInfoException(SVNException e) { + final SVNErrorCode errorCode = e.getErrorMessage().getErrorCode(); + if (!myLogExceptions || + SVNErrorCode.WC_PATH_NOT_FOUND.equals(errorCode) || + SVNErrorCode.UNVERSIONED_RESOURCE.equals(errorCode) || + SVNErrorCode.WC_NOT_WORKING_COPY.equals(errorCode)) { + LOG.debug(e); + } + else { + LOG.error(e); + } + } + + private SVNInfo getInfoSvnKit(@NotNull File ioFile) throws SVNException { + SVNInfo info = getInfoSvnKit(ioFile, SVNRevision.UNDEFINED); + if (info == null || info.getRepositoryRootURL() == null) { + info = getInfoSvnKit(ioFile, SVNRevision.HEAD); + } + return info; + } + + private SVNInfo getInfoSvnKit(@NotNull File ioFile, SVNRevision revision) throws SVNException { + return createWCClient().doInfo(ioFile, revision); + } + + private SVNInfo getInfoCommandLine(@NotNull File ioFile, SVNRevision revision) throws SVNException { + SvnCommandLineInfoClient client = new SvnCommandLineInfoClient(myProject); + return client.doInfo(ioFile, revision); + } + + private SvnWcClientI createInfoClient() { + return new SvnCommandLineInfoClient(myProject); + } + + public WorkingCopyFormat getWorkingCopyFormat(@NotNull File ioFile) { + RootUrlInfo rootInfo = getSvnFileUrlMapping().getWcRootForFilePath(ioFile); + WorkingCopyFormat format = rootInfo != null ? rootInfo.getFormat() : WorkingCopyFormat.UNKNOWN; + + if (WorkingCopyFormat.UNKNOWN.equals(format)) { + format = SvnFormatSelector.findRootAndGetFormat(ioFile); + } + + return format; } public void refreshSSLProperty() { @@ -1245,4 +1362,27 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { } return myCheckoutProvider; } + + /** + * Try to avoid usages of this method (for now) as it could not correctly for all cases + * detect svn 1.8 working copy format to guarantee command line client. + * + * For instance, when working copies of several formats are presented in project + * (though it seems to be rather unlikely case). + * + * @return + */ + public ClientFactory getFactory() { + // check working copy format of project directory + WorkingCopyFormat format = getWorkingCopyFormat(new File(getProject().getBaseDir().getPath())); + + return WorkingCopyFormat.ONE_DOT_EIGHT.equals(format) || + myConfiguration.myUseAcceleration.equals(SvnConfiguration.UseAcceleration.commandLine) ? cmdClientFactory : svnKitClientFactory; + } + + public ClientFactory getFactory(@NotNull File file) { + WorkingCopyFormat format = getWorkingCopyFormat(file); + + return WorkingCopyFormat.ONE_DOT_EIGHT.equals(format) ? cmdClientFactory : getFactory(); + } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnWriteOperationLocks.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnWriteOperationLocks.java index 36e0b08c5a86..ca97edb1c228 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnWriteOperationLocks.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnWriteOperationLocks.java @@ -30,6 +30,7 @@ import java.io.File; * Date: 10/23/12 * Time: 2:29 PM */ +// TODO: Such locking functionality is not required anymore. Likely to be removed. public class SvnWriteOperationLocks extends SvnAbstractWriteOperationLocks { private final RootsToWorkingCopies myRootsToWorkingCopies; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/WorkingCopyFormat.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/WorkingCopyFormat.java index d40f947a71ee..c007541816c5 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/WorkingCopyFormat.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/WorkingCopyFormat.java @@ -15,19 +15,22 @@ */ package org.jetbrains.idea.svn; -import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb; - /** * since not all constants are available from svnkit & constants are fixed */ public enum WorkingCopyFormat { + ONE_DOT_THREE(4, false, false, false, SvnBundle.message("dialog.show.svn.map.table.version13.text")), ONE_DOT_FOUR(8, false, false, false, SvnBundle.message("dialog.show.svn.map.table.version14.text")), ONE_DOT_FIVE(9, true, true, false, SvnBundle.message("dialog.show.svn.map.table.version15.text")), ONE_DOT_SIX(10, true, true, true, SvnBundle.message("dialog.show.svn.map.table.version16.text")), ONE_DOT_SEVEN(12, true, true, true, SvnBundle.message("dialog.show.svn.map.table.version17.text")), + ONE_DOT_EIGHT(12, true, true, true, SvnBundle.message("dialog.show.svn.map.table.version18.text")), UNKNOWN(0, false, false, false, "unknown"); + public static final int INTERNAL_FORMAT_17 = 29; + public static final int INTERNAL_FORMAT_18 = 31; + private final int myFormat; private final boolean myChangelistSupport; private final boolean myMergeInfoSupport; @@ -60,10 +63,11 @@ public enum WorkingCopyFormat { public static WorkingCopyFormat getInstance(final int value) { // somewhy 1.7 wc format can also be 29 - if (ISVNWCDb.WC_FORMAT_17 == value) { + if (INTERNAL_FORMAT_17 == value) { return ONE_DOT_SEVEN; - } - if (ONE_DOT_FIVE.getFormat() == value) { + } else if (INTERNAL_FORMAT_18 == value) { + return ONE_DOT_EIGHT; + } else if (ONE_DOT_FIVE.getFormat() == value) { return ONE_DOT_FIVE; } else if (ONE_DOT_FOUR.getFormat() == value) { return ONE_DOT_FOUR; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AddAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AddAction.java index 06c255b6ed38..7558a483fcd7 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AddAction.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AddAction.java @@ -30,8 +30,6 @@ import org.jetbrains.idea.svn.SvnBundle; import org.jetbrains.idea.svn.SvnStatusUtil; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.checkin.SvnCheckinEnvironment; -import org.tmatesoft.svn.core.SVNException; -import org.tmatesoft.svn.core.wc.SVNWCClient; import java.util.*; @@ -68,8 +66,6 @@ public class AddAction extends BasicAction { ProjectLevelVcsManager manager = ProjectLevelVcsManager.getInstance(project); manager.startBackgroundVcsOperation(); try { - - SVNWCClient wcClient = activeVcs.createWCClient(); final Set<VirtualFile> additionallyDirty = new HashSet<VirtualFile>(); final FileStatusManager fileStatusManager = FileStatusManager.getInstance(project); for (VirtualFile item : items) { @@ -84,13 +80,13 @@ public class AddAction extends BasicAction { } } } - Collection<SVNException> exceptions = - SvnCheckinEnvironment.scheduleUnversionedFilesForAddition(wcClient, Arrays.asList(items), true); + Collection<VcsException> exceptions = + SvnCheckinEnvironment.scheduleUnversionedFilesForAddition(activeVcs, Arrays.asList(items), true); additionallyDirty.addAll(Arrays.asList(items)); markDirty(project, additionallyDirty); if (!exceptions.isEmpty()) { final Collection<String> messages = new ArrayList<String>(exceptions.size()); - for (SVNException exception : exceptions) { + for (VcsException exception : exceptions) { messages.add(exception.getMessage()); } throw new VcsException(messages); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/MarkResolvedAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/MarkResolvedAction.java index 87b36970228b..d65c6d3c9c20 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/MarkResolvedAction.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/MarkResolvedAction.java @@ -19,6 +19,7 @@ package org.jetbrains.idea.svn.actions; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vcs.AbstractVcs; @@ -32,8 +33,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.svn.SvnBundle; import org.jetbrains.idea.svn.SvnStatusUtil; import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.idea.svn.conflict.ConflictClient; import org.jetbrains.idea.svn.dialogs.SelectFilesDialog; -import org.tmatesoft.svn.core.SVNDepth; +import org.jetbrains.idea.svn.portable.SvnStatusClientI; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.wc.*; @@ -42,6 +44,8 @@ import java.util.Collection; import java.util.TreeSet; public class MarkResolvedAction extends BasicAction { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.actions.MarkResolvedAction"); + protected String getActionName(AbstractVcs vcs) { return SvnBundle.message("action.name.mark.resolved"); } @@ -88,15 +92,13 @@ public class MarkResolvedAction extends BasicAction { } pathsArray = dialog.getSelectedPaths(); try { - SVNWCClient wcClient = vcs.createWCClient(); for (String path : pathsArray) { File ioFile = new File(path); - wcClient.doResolve(ioFile, SVNDepth.EMPTY, SVNConflictChoice.MERGED); + ConflictClient client = vcs.getFactory(ioFile).createConflictClient(); + + client.resolve(ioFile, true); } } - catch (SVNException e) { - throw new VcsException(e); - } finally { for (VirtualFile file : files) { VcsDirtyScopeManager.getInstance(project).fileDirty(file); @@ -115,10 +117,12 @@ public class MarkResolvedAction extends BasicAction { private static Collection<String> collectResolvablePaths(final SvnVcs vcs, VirtualFile[] files) { final Collection<String> target = new TreeSet<String>(); - SVNStatusClient stClient = vcs.createStatusClient(); for (VirtualFile file : files) { try { - stClient.doStatus(new File(file.getPath()), true, false, false, false, new ISVNStatusHandler() { + File path = new File(file.getPath()); + SvnStatusClientI client = vcs.getFactory(path).createStatusClient(); + + client.doStatus(path, true, false, false, false, new ISVNStatusHandler() { public void handleStatus(SVNStatus status) { if (status.getContentsStatus() == SVNStatusType.STATUS_CONFLICTED || status.getPropertiesStatus() == SVNStatusType.STATUS_CONFLICTED) { @@ -128,7 +132,7 @@ public class MarkResolvedAction extends BasicAction { }); } catch (SVNException e) { - // + LOG.warn(e); } } return target; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SvnMergeProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SvnMergeProvider.java index 4c582d01557d..3130dc948c13 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SvnMergeProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SvnMergeProvider.java @@ -27,12 +27,13 @@ import com.intellij.vcsUtil.VcsRunnable; import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.svn.SvnRevisionNumber; +import org.jetbrains.idea.svn.SvnUtil; import org.jetbrains.idea.svn.SvnVcs; -import org.tmatesoft.svn.core.SVNDepth; -import org.tmatesoft.svn.core.SVNException; +import org.jetbrains.idea.svn.properties.PropertyClient; import org.tmatesoft.svn.core.SVNProperty; import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.wc.*; +import org.tmatesoft.svn.core.wc2.SvnTarget; import java.io.ByteArrayOutputStream; import java.io.File; @@ -57,38 +58,34 @@ public class SvnMergeProvider implements MergeProvider { final MergeData data = new MergeData(); VcsRunnable runnable = new VcsRunnable() { public void run() throws VcsException { - SvnVcs vcs = SvnVcs.getInstance(myProject); File oldFile = null; File newFile = null; File workingFile = null; - SVNWCClient client; boolean mergeCase = false; - try { - client = vcs.createWCClient(); - SVNInfo info = client.doInfo(new File(file.getPath()), SVNRevision.UNDEFINED); - if (info != null) { - oldFile = info.getConflictOldFile(); - newFile = info.getConflictNewFile(); + SvnVcs vcs = SvnVcs.getInstance(myProject); + SVNInfo info = vcs.getInfo(file); + + if (info != null) { + oldFile = info.getConflictOldFile(); + newFile = info.getConflictNewFile(); + workingFile = info.getConflictWrkFile(); + mergeCase = workingFile == null || workingFile.getName().contains("working"); + // for debug + if (workingFile == null) { + LOG.info("Null working file when merging text conflict for " + file.getPath() + " old file: " + oldFile + " new file: " + newFile); + } + if (mergeCase) { + // this is merge case + oldFile = info.getConflictNewFile(); + newFile = info.getConflictOldFile(); workingFile = info.getConflictWrkFile(); - mergeCase = workingFile == null || workingFile.getName().contains("working"); - // for debug - if (workingFile == null) { - LOG.info("Null working file when merging text conflict for " + file.getPath() + " old file: " + oldFile + " new file: " + newFile); - } - if (mergeCase) { - // this is merge case - oldFile = info.getConflictNewFile(); - newFile = info.getConflictOldFile(); - workingFile = info.getConflictWrkFile(); - } - data.LAST_REVISION_NUMBER = new SvnRevisionNumber(info.getRevision()); } - } - catch (SVNException e) { - throw new VcsException(e); + data.LAST_REVISION_NUMBER = new SvnRevisionNumber(info.getRevision()); + } else { + throw new VcsException("Could not get info for " + file.getPath()); } if (oldFile == null || newFile == null || workingFile == null) { - ByteArrayOutputStream bos = getBaseRevisionContents(client, file); + ByteArrayOutputStream bos = getBaseRevisionContents(vcs, file); data.ORIGINAL = bos.toByteArray(); data.LAST = bos.toByteArray(); data.CURRENT = readFile(new File(file.getPath())); @@ -99,7 +96,7 @@ public class SvnMergeProvider implements MergeProvider { data.CURRENT = readFile(workingFile); } if (mergeCase) { - final ByteArrayOutputStream contents = getBaseRevisionContents(vcs.createWCClient(), file); + final ByteArrayOutputStream contents = getBaseRevisionContents(vcs, file); if (! Arrays.equals(contents.toByteArray(), data.ORIGINAL)) { // swap base and server: another order of merge arguments byte[] original = data.ORIGINAL; @@ -114,13 +111,17 @@ public class SvnMergeProvider implements MergeProvider { return data; } - private ByteArrayOutputStream getBaseRevisionContents(SVNWCClient client, VirtualFile file) { + private ByteArrayOutputStream getBaseRevisionContents(@NotNull SvnVcs vcs, @NotNull VirtualFile file) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { - client.doGetFileContents(new File(file.getPath()), SVNRevision.UNDEFINED, SVNRevision.BASE, true, bos); + byte[] contents = SvnUtil.getFileContents(vcs, SvnTarget.fromFile(new File(file.getPath())), SVNRevision.BASE, SVNRevision.UNDEFINED); + bos.write(contents); } - catch (SVNException e) { - // + catch (VcsException e) { + LOG.warn(e); + } + catch (IOException e) { + LOG.warn(e); } return bos; } @@ -135,13 +136,14 @@ public class SvnMergeProvider implements MergeProvider { } public void conflictResolvedForFile(VirtualFile file) { + // TODO: Add possibility to resolve content conflicts separately from property conflicts. SvnVcs vcs = SvnVcs.getInstance(myProject); + File path = new File(file.getPath()); try { - SVNWCClient client = vcs.createWCClient(); - client.doResolve(new File(file.getPath()), SVNDepth.EMPTY, true, false, SVNConflictChoice.MERGED); + vcs.getFactory(path).createConflictClient().resolve(path, false); } - catch (SVNException e) { - // + catch (VcsException e) { + LOG.warn(e); } // the .mine/.r## files have been deleted final VirtualFile parent = file.getParent(); @@ -152,17 +154,20 @@ public class SvnMergeProvider implements MergeProvider { public boolean isBinary(@NotNull final VirtualFile file) { SvnVcs vcs = SvnVcs.getInstance(myProject); + try { - SVNWCClient client = vcs.createWCClient(); File ioFile = new File(file.getPath()); - SVNPropertyData svnPropertyData = client.doGetProperty(ioFile, SVNProperty.MIME_TYPE, SVNRevision.UNDEFINED, SVNRevision.WORKING); + PropertyClient client = vcs.getFactory(ioFile).createPropertyClient(); + + SVNPropertyData svnPropertyData = client.getProperty(ioFile, SVNProperty.MIME_TYPE, false, SVNRevision.UNDEFINED, SVNRevision.WORKING); if (svnPropertyData != null && SVNProperty.isBinaryMimeType(SVNPropertyValue.getPropertyAsString(svnPropertyData.getValue()))) { return true; } } - catch (SVNException e) { - // + catch (VcsException e) { + LOG.warn(e); } + return false; } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/add/AddClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/add/AddClient.java new file mode 100644 index 000000000000..718a56dea9af --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/add/AddClient.java @@ -0,0 +1,23 @@ +package org.jetbrains.idea.svn.add; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.SvnClient; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; + +import java.io.File; + +/** + * @author Konstantin Kolosovsky. + */ +public interface AddClient extends SvnClient { + + void add(@NotNull File file, + @Nullable SVNDepth depth, + boolean makeParents, + boolean includeIgnored, + boolean force, + @Nullable ISVNEventHandler handler) throws VcsException; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/add/CmdAddClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/add/CmdAddClient.java new file mode 100644 index 000000000000..c7866713c9c1 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/add/CmdAddClient.java @@ -0,0 +1,69 @@ +package org.jetbrains.idea.svn.add; + +import com.intellij.openapi.vcs.VcsException; +import com.intellij.util.containers.Convertor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.jetbrains.idea.svn.api.FileStatusResultParser; +import org.jetbrains.idea.svn.commandLine.CommandUtil; +import org.jetbrains.idea.svn.commandLine.SvnCommandName; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; +import org.tmatesoft.svn.core.wc.SVNEvent; +import org.tmatesoft.svn.core.wc.SVNStatusType; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Konstantin Kolosovsky. + */ +public class CmdAddClient extends BaseSvnClient implements AddClient { + + private static final String STATUS = "\\s*(\\w)\\s*"; + private static final String OPTIONAL_FILE_TYPE = "(\\(.*\\))?"; + private static final String PATH = "\\s*(.*?)\\s*"; + private static final Pattern CHANGED_PATH = Pattern.compile(STATUS + OPTIONAL_FILE_TYPE + PATH); + + @Override + public void add(@NotNull File file, + @Nullable SVNDepth depth, + boolean makeParents, + boolean includeIgnored, + boolean force, + @Nullable ISVNEventHandler handler) throws VcsException { + List<String> parameters = prepareParameters(file, depth, makeParents, includeIgnored, force); + + // TODO: handler should be called in parallel with command execution, but this will be in other thread + // TODO: check if that is ok for current handler implementation + // TODO: add possibility to invoke "handler.checkCancelled" - process should be killed + CommandUtil.execute(myVcs, SvnCommandName.add, parameters, new FileStatusResultParser(CHANGED_PATH, handler, new AddStatusConvertor())); + } + + private static List<String> prepareParameters(File file, SVNDepth depth, boolean makeParents, boolean includeIgnored, boolean force) { + List<String> parameters = new ArrayList<String>(); + + CommandUtil.put(parameters, file); + CommandUtil.put(parameters, depth); + CommandUtil.put(parameters, makeParents, "--parents"); + CommandUtil.put(parameters, includeIgnored, "--no-ignore"); + CommandUtil.put(parameters, force, "--force"); + + return parameters; + } + + private static class AddStatusConvertor implements Convertor<Matcher, SVNEvent> { + @Override + public SVNEvent convert(Matcher o) { + SVNStatusType contentStatus = CommandUtil.getStatusType(o.group(1)); + String path = o.group(3); + + return new SVNEvent(new File(path), null, null, 0, contentStatus, null, null, null, null, null, null, null, + null, null, null); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/add/SvnKitAddClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/add/SvnKitAddClient.java new file mode 100644 index 000000000000..d6250c71f073 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/add/SvnKitAddClient.java @@ -0,0 +1,39 @@ +package org.jetbrains.idea.svn.add; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; + +/** + * @author Konstantin Kolosovsky. + */ +public class SvnKitAddClient extends BaseSvnClient implements AddClient { + + @Override + public void add(@NotNull File file, + @Nullable SVNDepth depth, + boolean makeParents, + boolean includeIgnored, + boolean force, + @Nullable ISVNEventHandler handler) throws VcsException { + try { + SVNWCClient client = myVcs.createWCClient(); + + client.setEventHandler(handler); + client.doAdd(file, force, + false, // directory should already be created + makeParents, // not used but will be passed as makeParents value + SVNDepth.recurseFromDepth(depth)); + } + catch (SVNException e) { + throw new VcsException(e); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/AnnotateClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/AnnotateClient.java new file mode 100644 index 000000000000..df30cfd89ae2 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/AnnotateClient.java @@ -0,0 +1,24 @@ +package org.jetbrains.idea.svn.annotate; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.SvnClient; +import org.tmatesoft.svn.core.wc.ISVNAnnotateHandler; +import org.tmatesoft.svn.core.wc.SVNDiffOptions; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc2.SvnTarget; + +/** + * @author Konstantin Kolosovsky. + */ +public interface AnnotateClient extends SvnClient { + + void annotate(@NotNull SvnTarget target, + @NotNull SVNRevision startRevision, + @NotNull SVNRevision endRevision, + @Nullable SVNRevision pegRevision, + boolean includeMergedRevisions, + @Nullable SVNDiffOptions diffOptions, + @Nullable ISVNAnnotateHandler handler) throws VcsException; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/CmdAnnotateClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/CmdAnnotateClient.java new file mode 100644 index 000000000000..d29d40eca84a --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/CmdAnnotateClient.java @@ -0,0 +1,126 @@ +package org.jetbrains.idea.svn.annotate; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.jetbrains.idea.svn.commandLine.CommandUtil; +import org.jetbrains.idea.svn.commandLine.SvnCommand; +import org.jetbrains.idea.svn.commandLine.SvnCommandName; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.ISVNAnnotateHandler; +import org.tmatesoft.svn.core.wc.SVNDiffOptions; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc2.SvnTarget; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * @author Konstantin Kolosovsky. + */ +public class CmdAnnotateClient extends BaseSvnClient implements AnnotateClient { + + @Override + public void annotate(@NotNull SvnTarget target, + @NotNull SVNRevision startRevision, + @NotNull SVNRevision endRevision, + @Nullable SVNRevision pegRevision, + boolean includeMergedRevisions, + @Nullable SVNDiffOptions diffOptions, + @Nullable final ISVNAnnotateHandler handler) throws VcsException { + // TODO: after merge remove setting includeMergedRevisions to false and update parsing + includeMergedRevisions = false; + + List<String> parameters = new ArrayList<String>(); + CommandUtil.put(parameters, target.getPathOrUrlString(), pegRevision); + parameters.add("--revision"); + parameters.add(startRevision + ":" + endRevision); + CommandUtil.put(parameters, includeMergedRevisions, "--use-merge-history"); + CommandUtil.put(parameters, diffOptions); + parameters.add("--xml"); + + SvnCommand command = CommandUtil.execute(myVcs, SvnCommandName.blame, parameters, null); + + parseOutput(command.getOutput(), handler); + } + + public void parseOutput(@NotNull String output, @Nullable ISVNAnnotateHandler handler) throws VcsException { + try { + JAXBContext context = JAXBContext.newInstance(BlameInfo.class); + Unmarshaller unmarshaller = context.createUnmarshaller(); + BlameInfo info = (BlameInfo)unmarshaller.unmarshal(new StringReader(output)); + + if (handler != null && info != null && info.target != null && info.target.lineEntries != null) { + for (LineEntry entry : info.target.lineEntries) { + invokeHandler(handler, entry); + } + } + } + catch (JAXBException e) { + throw new VcsException(e); + } + catch (SVNException e) { + throw new VcsException(e); + } + } + + private static void invokeHandler(ISVNAnnotateHandler handler, LineEntry entry) throws SVNException { + // line numbers in our api start from 0 - not from 1 like in svn output + handler.handleLine(entry.date(), entry.revision(), entry.author(), null, null, 0, null, null, entry.lineNumber - 1); + } + + @XmlRootElement(name = "blame") + public static class BlameInfo { + + @XmlElement(name = "target") + public TargetEntry target; + } + + public static class TargetEntry { + + @XmlElement(name = "entry") + List<LineEntry> lineEntries; + } + + public static class LineEntry { + + @XmlAttribute(name = "line-number") + public int lineNumber; + + @XmlElement(name = "commit") + public CommitEntry commit; + + public long revision() { + return commit != null ? commit.revision : 0; + } + + public String author() { + return commit != null ? commit.author : null; + } + + public Date date() { + return commit != null ? commit.date : null; + } + } + + public static class CommitEntry { + + @XmlAttribute(name = "revision") + public long revision; + + @XmlElement(name = "author") + public String author; + + @XmlElement(name = "date") + public Date date; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/SvnAnnotationProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/SvnAnnotationProvider.java index d0885c8a293e..88adf25e401c 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/SvnAnnotationProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/SvnAnnotationProvider.java @@ -32,14 +32,16 @@ import com.intellij.openapi.vcs.history.*; import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.*; +import org.jetbrains.idea.svn.history.HistoryClient; import org.jetbrains.idea.svn.history.SvnChangeList; import org.jetbrains.idea.svn.history.SvnFileRevision; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.wc.*; +import org.tmatesoft.svn.core.wc2.SvnTarget; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; @@ -89,9 +91,8 @@ public class SvnAnnotationProvider implements AnnotationProvider, VcsCacheableAn final String contents; if (loadExternally) { - final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - myVcs.createWCClient().doGetFileContents(ioFile, SVNRevision.UNDEFINED, SVNRevision.BASE, true, buffer); - contents = LoadTextUtil.getTextByBinaryPresentation(buffer.toByteArray(), file, false, false).toString(); + byte[] data = SvnUtil.getFileContents(myVcs, SvnTarget.fromFile(ioFile), SVNRevision.BASE, SVNRevision.UNDEFINED); + contents = LoadTextUtil.getTextByBinaryPresentation(data, file, false, false).toString(); } else { final byte[] bytes = VcsHistoryUtil.loadRevisionContent(revision); contents = LoadTextUtil.getTextByBinaryPresentation(bytes, file, false, false).toString(); @@ -99,16 +100,13 @@ public class SvnAnnotationProvider implements AnnotationProvider, VcsCacheableAn final SvnFileAnnotation result = new SvnFileAnnotation(myVcs, file, contents, lastChangedRevision); - SVNWCClient wcClient = myVcs.createWCClient(); - info = wcClient.doInfo(ioFile, SVNRevision.UNDEFINED); + info = myVcs.getInfo(ioFile); if (info == null) { exception[0] = new VcsException(new SVNException(SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "File ''{0}'' is not under version control", ioFile))); return; } final String url = info.getURL() == null ? null : info.getURL().toString(); - SVNLogClient client = myVcs.createLogClient(); - setLogClientOptions(client); SVNRevision endRevision = ((SvnFileRevision) revision).getRevision(); if (SVNRevision.WORKING.equals(endRevision)) { endRevision = info.getRevision(); @@ -122,14 +120,20 @@ public class SvnAnnotationProvider implements AnnotationProvider, VcsCacheableAn final boolean calculateMergeinfo = SvnConfiguration.getInstance(myVcs.getProject()).SHOW_MERGE_SOURCES_IN_ANNOTATE && SvnUtil.checkRepositoryVersion15(myVcs, url); - final SVNRevision svnRevision = ((SvnRevisionNumber)revision.getRevisionNumber()).getRevision(); + final MySteppedLogGetter logGetter = new MySteppedLogGetter( + myVcs, ioFile, progress, + myVcs.getFactory(ioFile).createHistoryClient(), endRevision, result, + url, calculateMergeinfo, file.getCharset()); - final MySteppedLogGetter logGetter = new MySteppedLogGetter(myVcs, ioFile, progress, client, endRevision, result, url, calculateMergeinfo, file.getCharset()); logGetter.go(); final LinkedList<SVNRevision> rp = logGetter.getRevisionPoints(); + // TODO: only 2 elements will be in rp and for loop will be executed only once - probably rewrite with Pair + AnnotateClient annotateClient = myVcs.getFactory(ioFile).createAnnotateClient(); for (int i = 0; i < rp.size() - 1; i++) { - client.doAnnotate(ioFile, svnRevision, rp.get(i + 1), rp.get(i), true, calculateMergeinfo, annotateHandler, null); + annotateClient.annotate(SvnTarget.fromFile(ioFile), rp.get(i + 1), rp.get(i), ((SvnFileRevision)revision).getPegRevision(), + calculateMergeinfo, + getLogClientOptions(myVcs), annotateHandler); } if (rp.get(1).getNumber() > 0) { @@ -137,31 +141,15 @@ public class SvnAnnotationProvider implements AnnotationProvider, VcsCacheableAn } annotation[0] = result; } - catch (SVNException e) { - if (SVNErrorCode.FS_NOT_FOUND.equals(e.getErrorMessage().getErrorCode())) { - final CommittedChangesProvider<SvnChangeList,ChangeBrowserSettings> provider = myVcs.getCommittedChangesProvider(); - try { - final Pair<SvnChangeList, FilePath> pair = provider.getOneList(file, revision.getRevisionNumber()); - if (pair != null && info != null && pair.getSecond() != null && ! Comparing.equal(pair.getSecond().getIOFile(), ioFile)) { - annotation[0] = annotateNonExisting(pair, revision, info, file.getCharset(), file); - return; - } - } - catch (VcsException e1) { - exception[0] = e1; - } - catch (SVNException e1) { - exception[0] = new VcsException(e); - } - catch (IOException e1) { - exception[0] = new VcsException(e); - } - } - exception[0] = new VcsException(e); - } catch (IOException e) { + catch (IOException e) { exception[0] = new VcsException(e); } catch (VcsException e) { - exception[0] = e; + if (e.getCause() instanceof SVNException) { + handleSvnException(ioFile, info, (SVNException)e.getCause(), file, revision, annotation, exception); + } + else { + exception[0] = e; + } } } }; @@ -177,6 +165,35 @@ public class SvnAnnotationProvider implements AnnotationProvider, VcsCacheableAn return annotation[0]; } + private void handleSvnException(File ioFile, + SVNInfo info, + SVNException e, + VirtualFile file, + VcsFileRevision revision, + FileAnnotation[] annotation, VcsException[] exception) { + // TODO: Check how this scenario could be reproduced by user and what changes needs to be done for command line client + if (SVNErrorCode.FS_NOT_FOUND.equals(e.getErrorMessage().getErrorCode())) { + final CommittedChangesProvider<SvnChangeList,ChangeBrowserSettings> provider = myVcs.getCommittedChangesProvider(); + try { + final Pair<SvnChangeList, FilePath> pair = provider.getOneList(file, revision.getRevisionNumber()); + if (pair != null && info != null && pair.getSecond() != null && ! Comparing.equal(pair.getSecond().getIOFile(), ioFile)) { + annotation[0] = annotateNonExisting(pair, revision, info, file.getCharset(), file); + return; + } + } + catch (VcsException e1) { + exception[0] = e1; + } + catch (SVNException e1) { + exception[0] = new VcsException(e); + } + catch (IOException e1) { + exception[0] = new VcsException(e); + } + } + exception[0] = new VcsException(e); + } + public static File getCommonAncestor(final File file1, final File file2) throws IOException { if (FileUtil.filesEqual(file1, file2)) return file1; final File can1 = file1.getCanonicalFile(); @@ -214,8 +231,7 @@ public class SvnAnnotationProvider implements AnnotationProvider, VcsCacheableAn final String relativePath = FileUtil.getRelativePath(root.getPath(), wasFile.getPath(), File.separatorChar); if (relativePath == null) throw new VcsException("Can not find relative path for " + wasFile.getPath() + "@" + revision.getRevisionNumber().asString()); - SVNWCClient wcClient = myVcs.createWCClient(); - SVNInfo wcRootInfo = wcClient.doInfo(root, SVNRevision.UNDEFINED); + SVNInfo wcRootInfo = myVcs.getInfo(root); if (wcRootInfo == null || wcRootInfo.getURL() == null) { throw new VcsException("Can not find relative path for " + wasFile.getPath() + "@" + revision.getRevisionNumber().asString()); } @@ -225,21 +241,18 @@ public class SvnAnnotationProvider implements AnnotationProvider, VcsCacheableAn wasUrl = wasUrl.appendPath(string, true); } - final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); final SVNRevision svnRevision = ((SvnRevisionNumber)revision.getRevisionNumber()).getRevision(); - myVcs.createWCClient().doGetFileContents(wasUrl, svnRevision, svnRevision, true, buffer); - final String contents = LoadTextUtil.getTextByBinaryPresentation(buffer.toByteArray(), - charset == null ? CharsetToolkit.UTF8_CHARSET : charset).toString(); - SVNLogClient client = myVcs.createLogClient(); - setLogClientOptions(client); + byte[] data = SvnUtil.getFileContents(myVcs, SvnTarget.fromURL(wasUrl), svnRevision, svnRevision); + final String contents = LoadTextUtil.getTextByBinaryPresentation(data, charset == null ? CharsetToolkit.UTF8_CHARSET : charset).toString(); final SvnRemoteFileAnnotation result = new SvnRemoteFileAnnotation(myVcs, contents, revision.getRevisionNumber(), pair.getFirst(), pair.getSecond().getPath(), current); final ISVNAnnotateHandler annotateHandler = createAnnotationHandler(ProgressManager.getInstance().getProgressIndicator(), result); final boolean calculateMergeinfo = SvnConfiguration.getInstance(myVcs.getProject()).SHOW_MERGE_SOURCES_IN_ANNOTATE && SvnUtil.checkRepositoryVersion15(myVcs, wasUrl.toString()); - client.doAnnotate(wasUrl, svnRevision, SVNRevision.create(1), svnRevision, true, calculateMergeinfo, annotateHandler, null); - + AnnotateClient client = myVcs.getFactory().createAnnotateClient(); + client.annotate(SvnTarget.fromURL(wasUrl), SVNRevision.create(1), svnRevision, svnRevision, calculateMergeinfo, + getLogClientOptions(myVcs), annotateHandler); return result; } @@ -370,14 +383,14 @@ public class SvnAnnotationProvider implements AnnotationProvider, VcsCacheableAn private final SvnVcs myVcs; private final File myIoFile; private final ProgressIndicator myProgress; - private final SVNLogClient myClient; + private final HistoryClient myClient; private final SVNRevision myEndRevision; private final boolean myCalculateMergeinfo; private final SvnFileAnnotation myResult; private final String myUrl; private final Charset myCharset; - private MySteppedLogGetter(final SvnVcs vcs, final File ioFile, final ProgressIndicator progress, final SVNLogClient client, + private MySteppedLogGetter(final SvnVcs vcs, final File ioFile, final ProgressIndicator progress, final HistoryClient client, final SVNRevision endRevision, final SvnFileAnnotation result, final String url, @@ -395,7 +408,7 @@ public class SvnAnnotationProvider implements AnnotationProvider, VcsCacheableAn myRevisionPoints = new LinkedList<SVNRevision>(); } - public void go() throws SVNException { + public void go() throws VcsException { final int maxAnnotateRevisions = SvnConfiguration.getInstance(myVcs.getProject()).getMaxAnnotateRevisions(); boolean longHistory = true; if (maxAnnotateRevisions == -1) { @@ -437,8 +450,8 @@ public class SvnAnnotationProvider implements AnnotationProvider, VcsCacheableAn myRevisionPoints.add(SVNRevision.create(0)); } - private void doLog(final boolean includeMerged, final SVNRevision truncateTo, final int max) throws SVNException { - myClient.doLog(new File[]{myIoFile}, myEndRevision, truncateTo == null ? SVNRevision.create(1L) : truncateTo, + private void doLog(final boolean includeMerged, final SVNRevision truncateTo, final int max) throws VcsException { + myClient.doLog(myIoFile, myEndRevision, truncateTo == null ? SVNRevision.create(1L) : truncateTo, SVNRevision.UNDEFINED, false, false, includeMerged, max, null, new ISVNLogEntryHandler() { public void handleLogEntry(SVNLogEntry logEntry) { @@ -464,9 +477,7 @@ public class SvnAnnotationProvider implements AnnotationProvider, VcsCacheableAn return true; } - private void setLogClientOptions(final SVNLogClient client) { - if (SvnConfiguration.getInstance(myVcs.getProject()).IGNORE_SPACES_IN_ANNOTATE) { - client.setDiffOptions(new SVNDiffOptions(true, true, true)); - } + private static SVNDiffOptions getLogClientOptions(@NotNull SvnVcs vcs) { + return SvnConfiguration.getInstance(vcs.getProject()).IGNORE_SPACES_IN_ANNOTATE ? new SVNDiffOptions(true, true, true) : null; } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/SvnKitAnnotateClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/SvnKitAnnotateClient.java new file mode 100644 index 000000000000..315178c9079e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/SvnKitAnnotateClient.java @@ -0,0 +1,42 @@ +package org.jetbrains.idea.svn.annotate; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.ISVNAnnotateHandler; +import org.tmatesoft.svn.core.wc.SVNDiffOptions; +import org.tmatesoft.svn.core.wc.SVNLogClient; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc2.SvnTarget; + +/** + * @author Konstantin Kolosovsky. + */ +public class SvnKitAnnotateClient extends BaseSvnClient implements AnnotateClient { + + @Override + public void annotate(@NotNull SvnTarget target, + @NotNull SVNRevision startRevision, + @NotNull SVNRevision endRevision, + @Nullable SVNRevision pegRevision, + boolean includeMergedRevisions, + @Nullable SVNDiffOptions diffOptions, + @Nullable ISVNAnnotateHandler handler) throws VcsException { + try { + SVNLogClient client = myVcs.createLogClient(); + + client.setDiffOptions(diffOptions); + if (target.isFile()) { + client.doAnnotate(target.getFile(), pegRevision, startRevision, endRevision, true, includeMergedRevisions, handler, null); + } + else { + client.doAnnotate(target.getURL(), pegRevision, startRevision, endRevision, true, includeMergedRevisions, handler, null); + } + } + catch (SVNException e) { + throw new VcsException(e); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/BaseSvnClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/BaseSvnClient.java new file mode 100644 index 000000000000..54401cff68b1 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/BaseSvnClient.java @@ -0,0 +1,22 @@ +package org.jetbrains.idea.svn.api; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.SvnVcs; + +/** + * @author Konstantin Kolosovsky. + */ +public abstract class BaseSvnClient implements SvnClient { + protected SvnVcs myVcs; + + @NotNull + @Override + public SvnVcs getVcs() { + return myVcs; + } + + @Override + public void setVcs(@NotNull SvnVcs vcs) { + myVcs = vcs; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/ClientFactory.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/ClientFactory.java new file mode 100644 index 000000000000..b85121637e6a --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/ClientFactory.java @@ -0,0 +1,99 @@ +package org.jetbrains.idea.svn.api; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.idea.svn.add.AddClient; +import org.jetbrains.idea.svn.annotate.AnnotateClient; +import org.jetbrains.idea.svn.conflict.ConflictClient; +import org.jetbrains.idea.svn.content.ContentClient; +import org.jetbrains.idea.svn.copy.CopyMoveClient; +import org.jetbrains.idea.svn.delete.DeleteClient; +import org.jetbrains.idea.svn.history.HistoryClient; +import org.jetbrains.idea.svn.portable.SvnStatusClientI; +import org.jetbrains.idea.svn.properties.PropertyClient; +import org.jetbrains.idea.svn.revert.RevertClient; + +/** + * @author Konstantin Kolosovsky. + */ +public abstract class ClientFactory { + + @NotNull + protected SvnVcs myVcs; + + protected AddClient addClient; + protected AnnotateClient annotateClient; + protected ContentClient contentClient; + protected HistoryClient historyClient; + protected RevertClient revertClient; + protected DeleteClient deleteClient; + protected SvnStatusClientI statusClient; + protected CopyMoveClient copyMoveClient; + protected ConflictClient conflictClient; + protected PropertyClient propertyClient; + + protected ClientFactory(@NotNull SvnVcs vcs) { + myVcs = vcs; + setup(); + } + + protected abstract void setup(); + + @NotNull + public AddClient createAddClient() { + return prepare(addClient); + } + + @NotNull + public AnnotateClient createAnnotateClient() { + return prepare(annotateClient); + } + + @NotNull + public ContentClient createContentClient() { + return prepare(contentClient); + } + + @NotNull + public HistoryClient createHistoryClient() { + return prepare(historyClient); + } + + @NotNull + public RevertClient createRevertClient() { + return prepare(revertClient); + } + + @NotNull + public SvnStatusClientI createStatusClient() { + // TODO: Update this in same like other clients - move to corresponding package, rename clients + return statusClient; + } + + @NotNull + public DeleteClient createDeleteClient() { + return prepare(deleteClient); + } + + @NotNull + public CopyMoveClient createCopyMoveClient() { + return prepare(copyMoveClient); + } + + @NotNull + public ConflictClient createConflictClient() { + return prepare(conflictClient); + } + + @NotNull + public PropertyClient createPropertyClient() { + return prepare(propertyClient); + } + + @NotNull + protected <T extends SvnClient> T prepare(@NotNull T client) { + client.setVcs(myVcs); + + return client; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/CmdClientFactory.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/CmdClientFactory.java new file mode 100644 index 000000000000..ca4088b6a733 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/CmdClientFactory.java @@ -0,0 +1,38 @@ +package org.jetbrains.idea.svn.api; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.idea.svn.add.CmdAddClient; +import org.jetbrains.idea.svn.annotate.CmdAnnotateClient; +import org.jetbrains.idea.svn.commandLine.SvnCommandLineStatusClient; +import org.jetbrains.idea.svn.conflict.CmdConflictClient; +import org.jetbrains.idea.svn.content.CmdContentClient; +import org.jetbrains.idea.svn.copy.CmdCopyMoveClient; +import org.jetbrains.idea.svn.delete.CmdDeleteClient; +import org.jetbrains.idea.svn.history.CmdHistoryClient; +import org.jetbrains.idea.svn.properties.CmdPropertyClient; +import org.jetbrains.idea.svn.revert.CmdRevertClient; + +/** + * @author Konstantin Kolosovsky. + */ +public class CmdClientFactory extends ClientFactory { + + public CmdClientFactory(@NotNull SvnVcs vcs) { + super(vcs); + } + + @Override + protected void setup() { + addClient = new CmdAddClient(); + annotateClient = new CmdAnnotateClient(); + contentClient = new CmdContentClient(); + historyClient = new CmdHistoryClient(); + revertClient = new CmdRevertClient(); + deleteClient = new CmdDeleteClient(); + copyMoveClient = new CmdCopyMoveClient(); + conflictClient = new CmdConflictClient(); + propertyClient = new CmdPropertyClient(); + statusClient = new SvnCommandLineStatusClient(myVcs.getProject()); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/FileStatusResultParser.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/FileStatusResultParser.java new file mode 100644 index 000000000000..fc202c265a6a --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/FileStatusResultParser.java @@ -0,0 +1,68 @@ +package org.jetbrains.idea.svn.api; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.util.containers.Convertor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; +import org.tmatesoft.svn.core.wc.SVNEvent; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Konstantin Kolosovsky. + */ +public class FileStatusResultParser { + + private static final double DEFAULT_PROGRESS = 0.0; + + @NotNull + private Pattern myLinePattern; + + @Nullable + private ISVNEventHandler handler; + + @NotNull + private Convertor<Matcher, SVNEvent> myConvertor; + + public FileStatusResultParser(@NotNull Pattern linePattern, + @Nullable ISVNEventHandler handler, + @NotNull Convertor<Matcher, SVNEvent> convertor) { + myLinePattern = linePattern; + this.handler = handler; + myConvertor = convertor; + } + + public void parse(@NotNull String output) throws VcsException { + if (StringUtil.isEmpty(output)) { + return; + } + + for (String line : StringUtil.splitByLines(output)) { + onLine(line); + } + } + + public void onLine(@NotNull String line) throws VcsException { + Matcher matcher = myLinePattern.matcher(line); + if (matcher.matches()) { + process(matcher); + } + else { + throw new VcsException("unknown state on line " + line); + } + } + + public void process(@NotNull Matcher matcher) throws VcsException { + if (handler != null) { + try { + handler.handleEvent(myConvertor.convert(matcher), DEFAULT_PROGRESS); + } catch (SVNException e) { + throw new VcsException(e); + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/SvnClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/SvnClient.java new file mode 100644 index 000000000000..90124f1cccb6 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/SvnClient.java @@ -0,0 +1,15 @@ +package org.jetbrains.idea.svn.api; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.SvnVcs; + +/** + * @author Konstantin Kolosovsky. + */ +public interface SvnClient { + + @NotNull + SvnVcs getVcs(); + + void setVcs(@NotNull SvnVcs vcs); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/SvnKitClientFactory.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/SvnKitClientFactory.java new file mode 100644 index 000000000000..7e1951893969 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/SvnKitClientFactory.java @@ -0,0 +1,38 @@ +package org.jetbrains.idea.svn.api; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.idea.svn.add.SvnKitAddClient; +import org.jetbrains.idea.svn.annotate.SvnKitAnnotateClient; +import org.jetbrains.idea.svn.conflict.SvnKitConflictClient; +import org.jetbrains.idea.svn.content.SvnKitContentClient; +import org.jetbrains.idea.svn.copy.SvnKitCopyMoveClient; +import org.jetbrains.idea.svn.delete.SvnKitDeleteClient; +import org.jetbrains.idea.svn.history.SvnKitHistoryClient; +import org.jetbrains.idea.svn.portable.SvnkitSvnStatusClient; +import org.jetbrains.idea.svn.properties.SvnKitPropertyClient; +import org.jetbrains.idea.svn.revert.SvnKitRevertClient; + +/** + * @author Konstantin Kolosovsky. + */ +public class SvnKitClientFactory extends ClientFactory { + + public SvnKitClientFactory(@NotNull SvnVcs vcs) { + super(vcs); + } + + @Override + protected void setup() { + addClient = new SvnKitAddClient(); + annotateClient = new SvnKitAnnotateClient(); + contentClient = new SvnKitContentClient(); + historyClient = new SvnKitHistoryClient(); + revertClient = new SvnKitRevertClient(); + deleteClient = new SvnKitDeleteClient(); + copyMoveClient = new SvnKitCopyMoveClient(); + conflictClient = new SvnKitConflictClient(); + propertyClient = new SvnKitPropertyClient(); + statusClient = new SvnkitSvnStatusClient(myVcs.createStatusClient()); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/DefaultConfigLoader.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/DefaultConfigLoader.java index 021568aac806..2b779fd0de70 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/DefaultConfigLoader.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/DefaultConfigLoader.java @@ -49,7 +49,7 @@ public class DefaultConfigLoader { final SvnVcs vcs = SvnVcs.getInstance(project); File rootFile = new File(vcsRoot.getPath()); - final SVNInfo info = vcs.createWCClient().doInfo(rootFile, SVNRevision.UNDEFINED); + final SVNInfo info = vcs.getInfo(rootFile); if (info == null || info.getURL() == null) { LOG.info("Directory is not a working copy: " + vcsRoot.getPresentableUrl()); return null; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationNew.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationNew.java index 28e74e7c395e..483bfda7cfd3 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationNew.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationNew.java @@ -223,9 +223,8 @@ public class SvnBranchConfigurationNew { private BranchRootSearcher(final SvnVcs vcs, final VirtualFile root) throws SVNException { myRoot = root; myBranchesUnder = new HashMap<String, String>(); - final SVNWCClient client = vcs.createWCClient(); - final SVNInfo info = client.doInfo(new File(myRoot.getPath()), SVNRevision.UNDEFINED); - myRootUrl = info.getURL(); + final SVNInfo info = vcs.getInfo(myRoot.getPath()); + myRootUrl = info != null ? info.getURL() : null; } public boolean accept(final String url) throws SVNException { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaCommitHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaCommitHandler.java index 3bd8256e7b02..5ac6b967777e 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaCommitHandler.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaCommitHandler.java @@ -16,8 +16,8 @@ package org.jetbrains.idea.svn.checkin; import com.intellij.openapi.progress.ProgressIndicator; -import org.jetbrains.idea.svn.CommitEventHandler; -import org.jetbrains.idea.svn.CommitEventType; +import org.jetbrains.idea.svn.commandLine.CommitEventHandler; +import org.jetbrains.idea.svn.commandLine.CommitEventType; import org.jetbrains.idea.svn.SvnBundle; import java.io.File; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaSvnkitBasedAuthenticationCallback.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaSvnkitBasedAuthenticationCallback.java index 995646f50d45..f765595745b3 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaSvnkitBasedAuthenticationCallback.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaSvnkitBasedAuthenticationCallback.java @@ -28,6 +28,7 @@ import com.intellij.util.net.HttpConfigurable; import com.intellij.util.proxy.CommonProxy; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.*; +import org.jetbrains.idea.svn.commandLine.AuthenticationCallback; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.auth.*; import org.tmatesoft.svn.core.internal.util.SVNBase64; @@ -77,6 +78,20 @@ public class IdeaSvnkitBasedAuthenticationCallback implements AuthenticationCall return new CredentialsAuthenticator(myVcs).tryAuthenticate(realm, url, file, previousFailed, passwordRequest); } + @Nullable + @Override + public SVNAuthentication requestCredentials(@Nullable SVNURL url, String type) { + SVNAuthentication authentication = + url != null ? myVcs.getSvnConfiguration().getInteractiveManager(myVcs).getProvider().requestClientAuthentication( + type, url, url.toDecodedString(), null, null, true) : null; + + if (authentication == null) { + LOG.warn("Could not get authentication. Type - " + type + ", Url - " + url); + } + + return authentication; + } + @Override public boolean acceptSSLServerCertificate(final File file, final String realm) { final File base = getExistingParent(file); @@ -354,7 +369,9 @@ public class IdeaSvnkitBasedAuthenticationCallback implements AuthenticationCall }, new ThrowableRunnable<SVNException>() { @Override public void run() throws SVNException { + // NOTE: DO NOT replace this call - SSL authentication highly tied to SVNKit myVcs.createWCClient(active).doInfo(myUrl, SVNRevision.UNDEFINED, SVNRevision.HEAD); + //myVcs.getInfo(myUrl, SVNRevision.HEAD, active); } } ); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/SvnCheckinEnvironment.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/SvnCheckinEnvironment.java index 433e4063e02c..c7d2d4b41bf2 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/SvnCheckinEnvironment.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/SvnCheckinEnvironment.java @@ -48,8 +48,8 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.*; +import org.jetbrains.idea.svn.commandLine.SvnBindClient; import org.jetbrains.idea.svn.commandLine.SvnCommandLineStatusClient; -import org.tigris.subversion.javahl.ClientException; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.wc.*; @@ -177,7 +177,7 @@ public class SvnCheckinEnvironment implements CheckinEnvironment { if (committables.isEmpty()) { return; } - if (WorkingCopyFormat.ONE_DOT_SEVEN.equals(format) && + if (WorkingCopyFormat.ONE_DOT_EIGHT.equals(format) || WorkingCopyFormat.ONE_DOT_SEVEN.equals(format) && SvnConfiguration.UseAcceleration.commandLine.equals(SvnConfiguration.getInstance(mySvnVcs.getProject()).myUseAcceleration) && (SvnAuthenticationManager.HTTP.equals(url.getProtocol()) || SvnAuthenticationManager.HTTPS.equals(url.getProtocol()))) { doWithCommandLine(committables, comment, exception, feedback); @@ -256,14 +256,25 @@ public class SvnCheckinEnvironment implements CheckinEnvironment { }); final IdeaSvnkitBasedAuthenticationCallback authenticationCallback = new IdeaSvnkitBasedAuthenticationCallback(mySvnVcs); try { - final SvnBindClient client = new SvnBindClient(SvnApplicationSettings.getInstance().getCommandLinePath()); + final SvnBindClient client = new SvnBindClient(SvnApplicationSettings.getInstance().getCommandLinePath(), new Convertor<String[], SVNURL>() { + @Override + public SVNURL convert(String[] o) { + SVNInfo info = o.length > 0 ? mySvnVcs.getInfo(o[0]) : null; + + if (info == null || info.getURL() == null) { + LOG.warn("Could not resolve repository url for commit. Paths - " + Arrays.toString(o)); + } + + return info != null ? info.getURL() : null; + } + }); client.setAuthenticationCallback(authenticationCallback); client.setHandler(new IdeaCommitHandler(ProgressManager.getInstance().getProgressIndicator())); final long revision = client.commit(ArrayUtil.toStringArray(paths), comment, false, false); reportCommittedRevisions(feedback, String.valueOf(revision)); } - catch (ClientException e) { - exception.add(new VcsException(e)); + catch (VcsException e) { + exception.add(e); } finally { authenticationCallback.reset(); } @@ -348,25 +359,19 @@ public class SvnCheckinEnvironment implements CheckinEnvironment { private List<File> getCommitables(List<File> paths) { final Adder adder = new Adder(); - SVNStatusClient statusClient = mySvnVcs.createStatusClient(); for (File path : paths) { File file = path.getAbsoluteFile(); adder.add(file); if (file.getParentFile() != null) { - addParents(statusClient, file.getParentFile(), adder); + addParents(file.getParentFile(), adder); } } return adder.getResult(); } - private static void addParents(SVNStatusClient statusClient, File file, final Adder adder) { - SVNStatus status; - try { - status = statusClient.doStatus(file, false); - } - catch (SVNException e) { - return; - } + private void addParents(File file, final Adder adder) { + SVNStatus status = getStatus(file); + if (status != null && (SvnVcs.svnStatusIs(status, SVNStatusType.STATUS_ADDED) || SvnVcs.svnStatusIs(status, SVNStatusType.STATUS_REPLACED))) { @@ -374,11 +379,33 @@ public class SvnCheckinEnvironment implements CheckinEnvironment { adder.add(file); file = file.getParentFile(); if (file != null) { - addParents(statusClient, file, adder); + addParents(file, adder); } } } + private SVNStatus getStatus(File file) { + SVNStatus result = null; + WorkingCopyFormat format = mySvnVcs.getWorkingCopyFormat(file); + + try { + result = WorkingCopyFormat.ONE_DOT_EIGHT.equals(format) ? getStatusCommandLine(file) : getStatusSvnKit(file); + } + catch (SVNException e) { + // do nothing + } + + return result; + } + + private SVNStatus getStatusSvnKit(File file) throws SVNException { + return mySvnVcs.createStatusClient().doStatus(file, false); + } + + private SVNStatus getStatusCommandLine(File file) throws SVNException { + return new SvnCommandLineStatusClient(mySvnVcs.getProject()).doStatus(file, false); + } + private static List<File> collectPaths(final List<Change> changes) { // case sensitive.. ArrayList<File> result = new ArrayList<File>(); @@ -418,15 +445,14 @@ public class SvnCheckinEnvironment implements CheckinEnvironment { public List<VcsException> scheduleMissingFileForDeletion(List<FilePath> filePaths) { List<VcsException> exceptions = new ArrayList<VcsException>(); - final SVNWCClient wcClient = mySvnVcs.createWCClient(); - List<File> files = ChangesUtil.filePathsToFiles(filePaths); + for (File file : files) { try { - wcClient.doDelete(file, true, false); + mySvnVcs.getFactory(file).createDeleteClient().delete(file, true); } - catch (SVNException e) { - exceptions.add(new VcsException(e)); + catch (VcsException e) { + exceptions.add(e); } } @@ -434,30 +460,22 @@ public class SvnCheckinEnvironment implements CheckinEnvironment { } public List<VcsException> scheduleUnversionedFilesForAddition(List<VirtualFile> files) { - final List<VcsException> result = new ArrayList<VcsException>(); - final SVNWCClient wcClient = mySvnVcs.createWCClient(); - - final List<SVNException> exceptionList = scheduleUnversionedFilesForAddition(wcClient, files); - for (SVNException svnException : exceptionList) { - result.add(new VcsException(svnException)); - } - return result; + return scheduleUnversionedFilesForAddition(mySvnVcs, files); } - public static List<SVNException> scheduleUnversionedFilesForAddition(SVNWCClient wcClient, List<VirtualFile> files) { - return scheduleUnversionedFilesForAddition(wcClient, files, false); + public static List<VcsException> scheduleUnversionedFilesForAddition(@NotNull SvnVcs vcs, List<VirtualFile> files) { + return scheduleUnversionedFilesForAddition(vcs, files, false); } - public static List<SVNException> scheduleUnversionedFilesForAddition(SVNWCClient wcClient, List<VirtualFile> files, final boolean recursive) { - List<SVNException> exceptions = new ArrayList<SVNException>(); - + public static List<VcsException> scheduleUnversionedFilesForAddition(@NotNull SvnVcs vcs, List<VirtualFile> files, final boolean recursive) { Collections.sort(files, FilePathComparator.getInstance()); - wcClient.setEventHandler(new ISVNEventHandler() { + ISVNEventHandler eventHandler = new ISVNEventHandler() { @Override public void handleEvent(SVNEvent event, double progress) throws SVNException { final ProgressManager pm = ProgressManager.getInstance(); final ProgressIndicator pi = pm.getProgressIndicator(); + // TODO: pi is null here when invoking "Add" action if (pi != null && event.getFile() != null) { File file = event.getFile(); pi.setText(SvnBundle.message("progress.text2.adding", file.getName() + " (" + file.getParent() + ")")); @@ -472,12 +490,18 @@ public class SvnCheckinEnvironment implements CheckinEnvironment { if (pi.isCanceled()) throw new SVNCancelException(); } } - }); + }; + + List<VcsException> exceptions = new ArrayList<VcsException>(); + for (VirtualFile file : files) { try { - wcClient.doAdd(new File(FileUtil.toSystemDependentName(file.getPath())), true, false, true, recursive); + File convertedFile = new File(FileUtil.toSystemDependentName(file.getPath())); + SVNDepth depth = recursive ? SVNDepth.INFINITY : SVNDepth.EMPTY; + + vcs.getFactory(convertedFile).createAddClient().add(convertedFile, depth, true, false, true, eventHandler); } - catch (SVNException e) { + catch (VcsException e) { exceptions.add(e); } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/AuthenticationCallback.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/AuthenticationCallback.java new file mode 100644 index 000000000000..1944749f3608 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/AuthenticationCallback.java @@ -0,0 +1,124 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.commandLine; + +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.auth.SVNAuthentication; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; + +/** + * Passed for authentication purpose to SvnLineCommand + * Called when svn command indicates that it needs credentials. It also means that credential was not found in standard place + * (Subversion config directory) + * + * Implementations should 1) ask credential from user or take it from any other storage (memory, for instance) + * 2) write credential in Subversion standard form into + * a) standard config directory if user allowed to save *all* credentials + * b) TMP directory and return path to the directory from getSpecialConfigDir() - if user rejected at least one credential storing + * + * Please note that several credentials could be asked during the command and therefore implementation class is used as + * keeping its state, and that TMP directory should be reused for all written credentials + * + * User: Irina.Chernushina + * Date: 2/26/13 + * Time: 1:05 PM + */ +public interface AuthenticationCallback { + /** + * Authenticate for realm and base file belonging to corresponding working copy + * + * @param realm - realm that should be used for credential retrieval/storage. + * @param base - file target of the operation + * @param previousFailed - whether previous credentials were correct + * @param passwordRequest - if true, password should be asked. Otherwise that may be a certificate (determined by the protocol) + * @return false if authentication canceled or was unsuccessful + */ + boolean authenticateFor(@Nullable String realm, File base, boolean previousFailed, boolean passwordRequest); + + /** + * Provides authentication information to access given url by authentication protocol identified by type. + * For instance, username/password for http/svn protocols. SSL client certificate for two way SSL protocol. + * + * @param url url to item in repository + * @param type authentication protocol type with svn specific values, like "svn.simple" for http. + * @return + */ + @Nullable + SVNAuthentication requestCredentials(@Nullable SVNURL url, String type); + + /** + * @return config directory if TMP was created + */ + @Nullable + File getSpecialConfigDir(); + + /** + * Ask user or read from memory storage whether server certificate should be accepted + * + * @param url - that we used for request + * @param realm - realm that should be used for credential retrieval/storage. + * @return true is certificate was accepted + */ + boolean acceptSSLServerCertificate(String url, final String realm); + + /** + * Ask user or read from memory storage whether server certificate should be accepted + * + * @param file - that we used for request + * @param realm - realm that should be used for credential retrieval/storage. + * @return true is certificate was accepted + */ + boolean acceptSSLServerCertificate(File file, final String realm); + + /** + * Clear credentials stored anywhere - in case they were not full, wrong or anything else + * + * @param realm - required that credential + * @param base - file used in command + * @param password - whether password credential should be deleted or certificate, if protocol might demand certificate + */ + void clearPassiveCredentials(String realm, File base, boolean password); + + /** + * @return true if there's something from IDEA config that should be persisted into Subversion tmp config directory + * for successful call + * (now it's IDEA proxy settings) + */ + boolean haveDataForTmpConfig(); + + /** + * writes IDEA config settings (that should be written) into tmp config directory + * (now it's IDEA proxy settings) + * @return true if have written data, false if wasn't able to determine parameters etc + * @throws IOException + * @throws URISyntaxException + */ + boolean persistDataToTmpConfig(File baseFile) throws IOException, URISyntaxException; + + /** + * Ask for IDEA-defined proxy credentials, using standard authenticator + * Store data into tmp config + * + * @return false if authentication was canceled or related calculations were unsuccessful + */ + boolean askProxyCredentials(File base); + + void reset(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandUtil.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandUtil.java new file mode 100644 index 000000000000..737d8a85b60b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandUtil.java @@ -0,0 +1,218 @@ +package org.jetbrains.idea.svn.commandLine; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.SvnApplicationSettings; +import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.idea.svn.api.FileStatusResultParser; +import org.jetbrains.idea.svn.checkin.IdeaSvnkitBasedAuthenticationCallback; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.wc.SVNDiffOptions; +import org.tmatesoft.svn.core.wc.SVNInfo; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNStatusType; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author Konstantin Kolosovsky. + */ +public class CommandUtil { + public static SvnLineCommand runSimple(@NotNull SvnCommandName name, + @NotNull SvnVcs vcs, + @Nullable File base, + @Nullable SVNURL url, + List<String> parameters) + throws SVNException { + String exe = resolveExePath(); + base = resolveBaseDirectory(base, exe); + url = resolveRepositoryUrl(vcs, url); + + try { + return SvnLineCommand + .runWithAuthenticationAttempt(exe, base, url, name, new SvnCommitRunner.CommandListener(null), + new IdeaSvnkitBasedAuthenticationCallback(vcs), ArrayUtil.toStringArray(parameters)); + } + catch (SvnBindException e) { + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e), e); + } + } + + @Nullable + private static SVNURL resolveRepositoryUrl(@NotNull SvnVcs vcs, @Nullable SVNURL url) { + if (url == null) { + // TODO: or take it from RootUrlInfo + SVNInfo info = vcs.getInfo(vcs.getProject().getBaseDir()); + + url = info != null ? info.getURL() : null; + } + return url; + } + + @NotNull + private static File resolveBaseDirectory(@Nullable File base, @NotNull String defaultBase) { + return base == null ? new File(defaultBase) : base; + } + + @NotNull + private static String resolveExePath() { + return SvnApplicationSettings.getInstance().getCommandLinePath(); + } + + public static SvnLineCommand runSimple(@NotNull SvnSimpleCommand command, @NotNull SvnVcs vcs, @Nullable File base, @Nullable SVNURL url) + throws SVNException { + // empty command name passed, as command name is already in command.getParameters() + return runSimple(SvnCommandName.empty, vcs, base, url, new ArrayList<String>(Arrays.asList(command.getParameters()))); + } + + /** + * Puts given value to parameters if condition is satisfied + * + * @param parameters + * @param condition + * @param value + */ + public static void put(@NotNull List<String> parameters, boolean condition, @NotNull String value) { + if (condition) { + parameters.add(value); + } + } + + public static void put(@NotNull List<String> parameters, @NotNull File path) { + parameters.add(path.getAbsolutePath()); + } + + public static void put(@NotNull List<String> parameters, @NotNull File path, @Nullable SVNRevision pegRevision) { + put(parameters, path.getAbsolutePath(), pegRevision); + } + + public static void put(@NotNull List<String> parameters, @NotNull String path, @Nullable SVNRevision pegRevision) { + StringBuilder builder = new StringBuilder(path); + + if (pegRevision != null && !SVNRevision.UNDEFINED.equals(pegRevision) && !SVNRevision.WORKING.equals(pegRevision) && + pegRevision.getNumber() > 0) { + builder.append("@"); + builder.append(pegRevision); + } + + parameters.add(builder.toString()); + } + + public static void put(@NotNull List<String> parameters, @NotNull File... paths) { + for (File path : paths) { + put(parameters, path); + } + } + + public static void put(@NotNull List<String> parameters, @Nullable SVNDepth depth) { + if (depth != null && !SVNDepth.UNKNOWN.equals(depth)) { + parameters.add("--depth"); + parameters.add(depth.getName()); + } + } + + public static void put(@NotNull List<String> parameters, @Nullable SVNRevision revision) { + if (revision != null && !SVNRevision.UNDEFINED.equals(revision) && !SVNRevision.WORKING.equals(revision) && revision.getNumber() >= 0) { + parameters.add("--revision"); + parameters.add(revision.toString()); + } + } + + public static void put(@NotNull List<String> parameters, @Nullable SVNDiffOptions diffOptions) { + if (diffOptions != null) { + StringBuilder builder = new StringBuilder(); + + if (diffOptions.isIgnoreAllWhitespace()) { + builder.append(" --ignore-space-change"); + } + if (diffOptions.isIgnoreAmountOfWhitespace()) { + builder.append(" --ignore-all-space"); + } + if (diffOptions.isIgnoreEOLStyle()) { + builder.append(" --ignore-eol-style"); + } + + String value = builder.toString().trim(); + + if (!StringUtil.isEmpty(value)) { + parameters.add("--extensions"); + parameters.add(value); + } + } + } + + /** + * Utility method for running commands changing certain file status information. + * // TODO: Should be replaced with non-static analogue. + * + * @param vcs + * @param name + * @param parameters + * @param parser + * @throws VcsException + */ + public static SvnCommand execute(@NotNull SvnVcs vcs, + @NotNull SvnCommandName name, + @NotNull List<String> parameters, + @Nullable FileStatusResultParser parser) + throws VcsException { + String exe = resolveExePath(); + File base = resolveBaseDirectory(null, exe); + SVNURL url = resolveRepositoryUrl(vcs, null); + + SvnLineCommand command = SvnLineCommand.runWithAuthenticationAttempt( + exe, base, url, name, new SvnCommitRunner.CommandListener(null), + new IdeaSvnkitBasedAuthenticationCallback(vcs), + ArrayUtil.toStringArray(parameters)); + + if (parser != null) { + parser.parse(command.getOutput()); + } + + return command; + } + + /** + * Gets svn status represented by single character. + * + * @param type + * @return + */ + public static char getStatusChar(@Nullable String type) { + return !StringUtil.isEmpty(type) ? type.charAt(0) : ' '; + } + + @NotNull + public static SVNStatusType getStatusType(@Nullable String type) { + return getStatusType(getStatusChar(type)); + } + + @NotNull + public static SVNStatusType getStatusType(char first) { + final SVNStatusType contentsStatus; + if ('A' == first) { + contentsStatus = SVNStatusType.STATUS_ADDED; + } else if ('D' == first) { + contentsStatus = SVNStatusType.STATUS_DELETED; + } else if ('U' == first) { + contentsStatus = SVNStatusType.CHANGED; + } else if ('C' == first) { + contentsStatus = SVNStatusType.CONFLICTED; + } else if ('G' == first) { + contentsStatus = SVNStatusType.MERGED; + } else if ('R' == first) { + contentsStatus = SVNStatusType.STATUS_REPLACED; + } else if ('E' == first) { + contentsStatus = SVNStatusType.STATUS_OBSTRUCTED; + } else { + contentsStatus = SVNStatusType.STATUS_NORMAL; + } + return contentsStatus; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/SvnPropDetailsProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommitEventHandler.java index 683024679277..d74884073863 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/SvnPropDetailsProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommitEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jetbrains.idea.svn.properties; +package org.jetbrains.idea.svn.commandLine; + +import java.io.File; /** - * Created with IntelliJ IDEA. + * Used to listen to commit events to display progress to user + * * User: Irina.Chernushina - * Date: 2/12/12 - * Time: 8:40 PM + * Date: 2/26/13 + * Time: 10:12 AM */ -public class SvnPropDetailsProvider { +public interface CommitEventHandler { + void commitEvent(final CommitEventType type, final File target); + void committedRevision(final long revNum); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommitEventType.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommitEventType.java new file mode 100644 index 000000000000..87dc212d0341 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommitEventType.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.commandLine; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/25/13 + * Time: 6:51 PM + */ +public enum CommitEventType { + adding("Adding"), + deleting("Deleting"), + sending("Sending"), + replacing("Replacing"), + transmittingDeltas("Transmitting file data"), + committedRevision("Committed revision"); + + private final String myText; + + CommitEventType(String text) { + myText = text; + } + + public String getText() { + return myText; + } + + public static CommitEventType create(String text) { + text = text.trim(); + for (CommitEventType value : CommitEventType.values()) { + if (value.getText().equals(text)) return value; + } + return null; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/LineCommandListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/LineCommandListener.java new file mode 100644 index 000000000000..ccbc5c37db61 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/LineCommandListener.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.commandLine; + +import com.intellij.openapi.vcs.LineProcessEventListenerAdapter; + +import java.io.File; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/26/13 + * Time: 10:38 AM + */ +public abstract class LineCommandListener extends LineProcessEventListenerAdapter { + private boolean myCanceled; + + public abstract void baseDirectory(final File file); + + public void cancel() { + myCanceled = true; + } + + protected boolean isCanceled() { + return myCanceled; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnBindClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnBindClient.java new file mode 100644 index 000000000000..8f14cb269179 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnBindClient.java @@ -0,0 +1,67 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.commandLine; + +import com.intellij.openapi.vcs.VcsException; +import com.intellij.util.containers.Convertor; +import org.tmatesoft.svn.core.SVNURL; + +import java.util.Map; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/5/13 + * Time: 3:08 PM + */ +public class SvnBindClient { + private final String myExecutablePath; + private CommitEventHandler myHandler; + private AuthenticationCallback myAuthenticationCallback; + private Convertor<String[], SVNURL> myUrlProvider; + + public SvnBindClient(String path, Convertor<String[], SVNURL> urlProvider) { + myExecutablePath = path; + myUrlProvider = urlProvider; + } + + public long commit(String[] path, String message, boolean recurse, boolean noUnlock) throws VcsException { + return commit(path, message, recurse? 3 : 0, noUnlock, false, null, null); + } + + public long commit(String[] path, + String message, + int depth, + boolean noUnlock, + boolean keepChangelist, + String[] changelists, + Map revpropTable) throws VcsException { + final long commit = new SvnCommitRunner(myExecutablePath, myHandler, myAuthenticationCallback). + commit(path, message, depth, noUnlock, keepChangelist, changelists, revpropTable, myUrlProvider); + if (commit < 0) { + throw new VcsException("Wrong committed revision number: " + commit); + } + return commit; + } + + public void setHandler(CommitEventHandler handler) { + myHandler = handler; + } + + public void setAuthenticationCallback(AuthenticationCallback authenticationCallback) { + myAuthenticationCallback = authenticationCallback; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnBindException.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnBindException.java new file mode 100644 index 000000000000..524b928eae92 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnBindException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.commandLine; + +import com.intellij.openapi.vcs.VcsException; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/25/13 + * Time: 5:57 PM + * + * Marker exception + */ +public class SvnBindException extends VcsException { + public SvnBindException(String message) { + super(message); + } + + public SvnBindException(Throwable throwable) { + super(throwable); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnBindUtil.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnBindUtil.java new file mode 100644 index 000000000000..7d663bae65b5 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnBindUtil.java @@ -0,0 +1,82 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.commandLine; + +import java.io.File; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/5/13 + * Time: 4:56 PM + */ +public class SvnBindUtil { + + private final static List<DateFormat> ourFormats = new ArrayList<DateFormat>(); + + static { + ourFormats.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")); + ourFormats.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'000Z'")); + ourFormats.add(new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", Locale.US)); + ourFormats.add(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z (EE, d MMM yyyy)", Locale.getDefault())); + ourFormats.add(new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss' 'ZZZZ' ('E', 'dd' 'MMM' 'yyyy')'")); + ourFormats.add(new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss'Z'")); + ourFormats.add(new SimpleDateFormat("EEE' 'MMM' 'dd' 'HH:mm:ss' 'yyyy")); + ourFormats.add(new SimpleDateFormat("MM' 'dd' 'yyyy")); + ourFormats.add(new SimpleDateFormat("MM' 'dd' 'HH:mm")); + ourFormats.add(new SimpleDateFormat("MM' 'dd' 'HH:mm:ss")); + } + + public static Date parseDate(final String date) { + for (DateFormat format : ourFormats) { + try { + return format.parse(date); + } + catch (ParseException e) { + continue; + } + } + return new Date(0); + } + + public static void changelistsToCommand(String[] changeLists, final List<String> list) { + if (changeLists != null) { + for (String name : changeLists) { + list.add("--cl"); + list.add(name); + } + } + } + + public static String getDepthName(final int i) { + return org.tigris.subversion.javahl.Depth.toADepth(i).name(); + } + + public static File correctUpToExistingParent(File base) { + while (base != null) { + if (base.exists() && base.isDirectory()) return base; + base = base.getParentFile(); + } + return null; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommand.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommand.java new file mode 100644 index 000000000000..9e355e56f4d6 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommand.java @@ -0,0 +1,272 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.commandLine; + +import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.execution.process.CapturingProcessAdapter; +import com.intellij.execution.process.OSProcessHandler; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.ProcessListener; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.vcs.ProcessEventListener; +import com.intellij.util.EventDispatcher; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 1/25/12 + * Time: 12:58 PM + */ +public abstract class SvnCommand { + static final Logger LOG = Logger.getInstance(SvnCommand.class.getName()); + private final File myConfigDir; + + private boolean myIsDestroyed; + private int myExitCode; + protected final GeneralCommandLine myCommandLine; + private final File myWorkingDirectory; + private Process myProcess; + private OSProcessHandler myHandler; + // TODO: Try to implement commands in a way that they manually indicate if they need full output - to prevent situations + // TODO: when large amount of data needs to be stored instead of just sequential processing. + private CapturingProcessAdapter outputAdapter; + private final Object myLock; + + private final EventDispatcher<ProcessEventListener> myListeners = EventDispatcher.create(ProcessEventListener.class); + private final SvnCommandName myCommandName; + + public SvnCommand(File workingDirectory, @NotNull SvnCommandName commandName, @NotNull @NonNls String exePath) { + this(workingDirectory, commandName, exePath, null); + } + + public SvnCommand(File workingDirectory, @NotNull SvnCommandName commandName, @NotNull @NonNls String exePath, + @Nullable File configDir) { + myCommandName = commandName; + myLock = new Object(); + myCommandLine = new GeneralCommandLine(); + myWorkingDirectory = workingDirectory; + myCommandLine.setExePath(exePath); + myCommandLine.setWorkDirectory(workingDirectory); + myConfigDir = configDir; + if (configDir != null) { + myCommandLine.addParameters("--config-dir", configDir.getPath()); + } + if (!SvnCommandName.empty.equals(commandName)) { + myCommandLine.addParameter(commandName.getName()); + } + } + + public String[] getParameters() { + synchronized (myLock) { + return myCommandLine.getParametersList().getArray(); + } + } + + /** + * Indicates if process was destroyed "manually" by command execution logic. + * + * @return + */ + public boolean isManuallyDestroyed() { + return myIsDestroyed; + } + + public void start() { + synchronized (myLock) { + checkNotStarted(); + + try { + myProcess = myCommandLine.createProcess(); + if (LOG.isDebugEnabled()) { + LOG.debug(myCommandLine.toString()); + } + myHandler = new OSProcessHandler(myProcess, myCommandLine.getCommandLineString()); + startHandlingStreams(); + } catch (Throwable t) { + myListeners.getMulticaster().startFailed(t); + } + } + } + + private void startHandlingStreams() { + final ProcessListener processListener = new ProcessListener() { + public void startNotified(final ProcessEvent event) { + // do nothing + } + + public void processTerminated(final ProcessEvent event) { + final int exitCode = event.getExitCode(); + try { + setExitCode(exitCode); + SvnCommand.this.processTerminated(exitCode); + } finally { + listeners().processTerminated(exitCode); + } + } + + public void processWillTerminate(final ProcessEvent event, final boolean willBeDestroyed) { + // do nothing + } + + public void onTextAvailable(final ProcessEvent event, final Key outputType) { + SvnCommand.this.onTextAvailable(event.getText(), outputType); + } + }; + + outputAdapter = new CapturingProcessAdapter(); + myHandler.addProcessListener(outputAdapter); + myHandler.addProcessListener(processListener); + myHandler.startNotify(); + } + + public String getOutput() { + return outputAdapter.getOutput().getStdout(); + } + + /** + * Wait for process termination + * @param timeout + */ + public boolean waitFor(int timeout) { + checkStarted(); + final OSProcessHandler handler; + synchronized (myLock) { + if (myIsDestroyed) return true; + handler = myHandler; + } + if (timeout == -1) { + return handler.waitFor(); + } + else { + return handler.waitFor(timeout); + } + } + + protected abstract void processTerminated(int exitCode); + protected abstract void onTextAvailable(final String text, final Key outputType); + + public void cancel() { + synchronized (myLock) { + checkStarted(); + destroyProcess(); + } + } + + protected void setExitCode(final int code) { + synchronized (myLock) { + myExitCode = code; + } + } + + public void addListener(final ProcessEventListener listener) { + synchronized (myLock) { + myListeners.addListener(listener); + } + } + + protected ProcessEventListener listeners() { + synchronized (myLock) { + return myListeners.getMulticaster(); + } + } + + public void addParameters(@NonNls @NotNull String... parameters) { + synchronized (myLock) { + checkNotStarted(); + myCommandLine.addParameters(parameters); + } + } + + public void addParameters(List<String> parameters) { + synchronized (myLock) { + checkNotStarted(); + myCommandLine.addParameters(parameters); + } + } + + public void destroyProcess() { + synchronized (myLock) { + if (! myIsDestroyed) { + myIsDestroyed = true; + myHandler.destroyProcess(); + } + } + } + + public String getCommandText() { + synchronized (myLock) { + return myCommandLine.getCommandLineString(); + } + } + + public String getExePath() { + synchronized (myLock) { + return myCommandLine.getExePath(); + } + } + + /** + * check that process is not started yet + * + * @throws IllegalStateException if process has been already started + */ + private void checkNotStarted() { + if (isStarted()) { + throw new IllegalStateException("The process has been already started"); + } + } + + /** + * check that process is started + * + * @throws IllegalStateException if process has not been started + */ + protected void checkStarted() { + if (! isStarted()) { + throw new IllegalStateException("The process is not started yet"); + } + } + + /** + * @return true if process is started + */ + public boolean isStarted() { + synchronized (myLock) { + return myProcess != null; + } + } + + protected int getExitCode() { + synchronized (myLock) { + return myExitCode; + } + } + + protected File getWorkingDirectory() { + return myWorkingDirectory; + } + + public SvnCommandName getCommandName() { + return myCommandName; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineInfoClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineInfoClient.java index 0d2f4d00bd39..47b6b5234396 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineInfoClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineInfoClient.java @@ -15,12 +15,13 @@ */ package org.jetbrains.idea.svn.commandLine; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.util.Consumer; -import org.jetbrains.idea.svn.SvnBindUtil; +import org.jetbrains.idea.svn.SvnApplicationSettings; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.portable.SvnExceptionWrapper; import org.jetbrains.idea.svn.portable.SvnkitSvnWcClient; @@ -36,7 +37,9 @@ import javax.xml.parsers.SAXParserFactory; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; /** * Created with IntelliJ IDEA. @@ -45,9 +48,12 @@ import java.util.Collection; * Time: 12:59 PM */ public class SvnCommandLineInfoClient extends SvnkitSvnWcClient { + + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.commandLine.SvnCommandLineInfoClient"); private final Project myProject; public SvnCommandLineInfoClient(final Project project) { + // TODO: Remove svn kit client instantiation super(SvnVcs.getInstance(project).createWCClient()); myProject = project; } @@ -74,22 +80,56 @@ public class SvnCommandLineInfoClient extends SvnkitSvnWcClient { base = SvnBindUtil.correctUpToExistingParent(base); if (base == null) { // very unrealistic - throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), new RuntimeException("Can not find existing parent file")); + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Can not find existing parent file")); } + issueCommand(path.getAbsolutePath(), pegRevision, revision, depth, changeLists, handler, base); + } + + private void issueCommand(String path, SVNRevision pegRevision, + SVNRevision revision, + SVNDepth depth, + Collection changeLists, + final ISVNInfoHandler handler, File base) throws SVNException { final SvnSimpleCommand command = SvnCommandFactory.createSimpleCommand(myProject, base, SvnCommandName.info); + List<String> parameters = new ArrayList<String>(); + + fillParameters(path, pegRevision, revision, depth, parameters); + command.addParameters(parameters); + SvnCommandLineStatusClient.changelistsToCommand(changeLists, command); - if (depth != null) { - command.addParameters("--depth", depth.getName()); + parseResult(handler, base, execute(command)); + } + + private String execute(SvnSimpleCommand command) throws SVNException { + try { + return command.run(); } - if (revision != null && ! SVNRevision.UNDEFINED.equals(revision) && ! SVNRevision.WORKING.equals(revision)) { - command.addParameters("-r", revision.toString()); + catch (VcsException e) { + final String text = e.getMessage(); + final boolean notEmpty = !StringUtil.isEmptyOrSpaces(text); + if (notEmpty && text.contains("W155010")) { + // just null + return null; + } + // not a working copy exception + // "E155007: '' is not a working copy" + if (notEmpty && text.contains("is not a working copy")) { + if (StringUtil.isNotEmpty(command.getOutput())) { + // workaround: as in subversion 1.8 "svn info" on a working copy root outputs such error for parent folder, + // if there are files with conflicts. + // but the requested info is still in the output except root closing tag + return command.getOutput() + "</info>"; + } else { + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.WC_NOT_WORKING_COPY, e), e); + } + } + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e), e); } - command.addParameters("--xml"); - SvnCommandLineStatusClient.changelistsToCommand(changeLists, command); - if (pegRevision != null && ! SVNRevision.UNDEFINED.equals(pegRevision) && ! SVNRevision.WORKING.equals(pegRevision)) { - command.addParameters(path.getPath() + "@" + pegRevision.toString()); - } else { - command.addParameters(path.getPath()); + } + + private void parseResult(final ISVNInfoHandler handler, File base, String result) throws SVNException { + if (StringUtil.isEmpty(result)) { + return; } final SvnInfoHandler[] infoHandler = new SvnInfoHandler[1]; @@ -106,38 +146,34 @@ public class SvnCommandLineInfoClient extends SvnkitSvnWcClient { }); try { - final String result = command.run(); SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); - parser.parse(new ByteArrayInputStream(result.getBytes(CharsetToolkit.UTF8_CHARSET)), infoHandler[0]); + parser.parse(new ByteArrayInputStream(result.getBytes(CharsetToolkit.UTF8_CHARSET)), infoHandler[0]); } catch (SvnExceptionWrapper e) { + LOG.info("info output " + result); throw (SVNException) e.getCause(); } catch (IOException e) { - throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e); + LOG.info("info output " + result); + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e), e); } catch (ParserConfigurationException e) { - throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e); + LOG.info("info output " + result); + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e), e); } catch (SAXException e) { - throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e); - } - catch (VcsException e) { - final String text = e.getMessage(); - final boolean notEmpty = !StringUtil.isEmptyOrSpaces(text); - if (notEmpty && text.contains("W155010")) { - // just null - return; - } - // not a working copy exception - // "E155007: '' is not a working copy" - if (notEmpty && text.contains("is not a working copy")) { - throw new SVNException(SVNErrorMessage.create(SVNErrorCode.WC_NOT_WORKING_COPY), e); - } - throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e); + LOG.info("info output " + result); + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e), e); } } + private void fillParameters(String path, SVNRevision pegRevision, SVNRevision revision, SVNDepth depth, List<String> parameters) { + CommandUtil.put(parameters, depth); + CommandUtil.put(parameters, revision); + CommandUtil.put(parameters, path, pegRevision); + parameters.add("--xml"); + } + @Override public void doInfo(SVNURL url, SVNRevision pegRevision, SVNRevision revision, boolean recursive, ISVNInfoHandler handler) throws SVNException { @@ -147,7 +183,14 @@ public class SvnCommandLineInfoClient extends SvnkitSvnWcClient { @Override public void doInfo(SVNURL url, SVNRevision pegRevision, SVNRevision revision, SVNDepth depth, ISVNInfoHandler handler) throws SVNException { - throw new UnsupportedOperationException(); + String path = url.toDecodedString(); + List<String> parameters = new ArrayList<String>(); + + fillParameters(path, pegRevision, revision, depth, parameters); + File base = new File(SvnApplicationSettings.getInstance().getCommandLinePath()); + String result = CommandUtil.runSimple(SvnCommandName.info, SvnVcs.getInstance(myProject), base, url, parameters).getOutput(); + + parseResult(handler, base, result); } @Override diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineStatusClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineStatusClient.java index a1a31536c8a9..c1429be699af 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineStatusClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineStatusClient.java @@ -15,14 +15,16 @@ */ package org.jetbrains.idea.svn.commandLine; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Getter; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.util.containers.Convertor; -import org.jetbrains.idea.svn.SvnBindUtil; +import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.svn.SvnUtil; +import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.portable.PortableStatus; import org.jetbrains.idea.svn.portable.SvnExceptionWrapper; import org.jetbrains.idea.svn.portable.SvnStatusClientI; @@ -48,6 +50,8 @@ import java.util.Map; * Time: 5:21 PM */ public class SvnCommandLineStatusClient implements SvnStatusClientI { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.commandLine.SvnCommandLineStatusClient"); + private final Project myProject; private final SvnCommandLineInfoClient myInfoClient; @@ -102,47 +106,84 @@ public class SvnCommandLineStatusClient implements SvnStatusClientI { final SVNInfo infoBase = myInfoClient.doInfo(base, revision); final SvnSimpleCommand command = SvnCommandFactory.createSimpleCommand(myProject, base, SvnCommandName.st); - putParameters(depth, remote, reportAll, includeIgnored, changeLists, command); + putParameters(path, depth, remote, reportAll, includeIgnored, changeLists, command); + + parseResult(path, revision, handler, base, infoBase, command, execute(command, base)); + return 0; + } + + private String execute(SvnSimpleCommand command, File base) throws SVNException { + String result = CommandUtil.runSimple(command, SvnVcs.getInstance(myProject), base, null).getOutput(); - final SvnStatusHandler[] svnHandl = new SvnStatusHandler[1]; - svnHandl[0] = createStatusHandler(revision, handler, base, infoBase, svnHandl); + if (StringUtil.isEmptyOrSpaces(result)) { + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.FS_GENERAL, "Status request returned nothing for command: " + + command.myCommandLine.getCommandLineString())); + } + + return result; + } + + private void parseResult(final File path, + SVNRevision revision, + ISVNStatusHandler handler, + File base, + SVNInfo infoBase, + SvnSimpleCommand command, String result) throws SVNException { + + if (StringUtil.isEmpty(result)) { + return; + } try { - final String result = command.run(); - if (StringUtil.isEmptyOrSpaces(result)) { - throw new VcsException("Status request returned nothing for command: " + command.myCommandLine.getCommandLineString()); - } + final SvnStatusHandler[] svnHandl = new SvnStatusHandler[1]; + svnHandl[0] = createStatusHandler(revision, handler, base, infoBase, svnHandl); SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); parser.parse(new ByteArrayInputStream(result.getBytes(CharsetToolkit.UTF8_CHARSET)), svnHandl[0]); - if (! svnHandl[0].isAnythingReported()) { - if (! SvnUtil.isSvnVersioned(myProject, path)) { - throw new SVNException(SVNErrorMessage.create(SVNErrorCode.WC_NOT_DIRECTORY)); + if (!svnHandl[0].isAnythingReported()) { + if (!SvnUtil.isSvnVersioned(myProject, path)) { + throw new SVNException( + SVNErrorMessage.create(SVNErrorCode.WC_NOT_DIRECTORY, "Command - " + command.getCommandText() + ". Result - " + result)); + } else { + // return status indicating "NORMAL" state + // typical output would be like + // <status> + // <target path="1.txt"></target> + // </status> + // so it does not contain any <entry> element and current parsing logic returns null + + PortableStatus status = new PortableStatus(); + status.setPath(path.getAbsolutePath()); + status.setContentsStatus(SVNStatusType.STATUS_NORMAL); + status.setInfoGetter(new Getter<SVNInfo>() { + @Override + public SVNInfo get() { + return createInfoGetter(null).convert(path); + } + }); + handler.handleStatus(status); } } } catch (SvnExceptionWrapper e) { throw (SVNException) e.getCause(); } catch (IOException e) { - throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e); + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e), e); } catch (ParserConfigurationException e) { - throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e); + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e), e); } catch (SAXException e) { - throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e); - } - catch (VcsException e) { - throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e); + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e), e); } - return 0; } - private void putParameters(SVNDepth depth, + private void putParameters(@NotNull File path, SVNDepth depth, boolean remote, boolean reportAll, boolean includeIgnored, Collection changeLists, SvnSimpleCommand command) { + command.addParameters(path.getAbsolutePath()); if (depth != null) { command.addParameters("--depth", depth.getName()); } @@ -171,7 +212,11 @@ public class SvnCommandLineStatusClient implements SvnStatusClientI { final SVNInfo infoBase, final SvnStatusHandler[] svnHandl) { final SvnStatusHandler.ExternalDataCallback callback = createStatusCallback(handler, base, infoBase, svnHandl); - return new SvnStatusHandler(callback, base, new Convertor<File, SVNInfo>() { + return new SvnStatusHandler(callback, base, createInfoGetter(revision)); + } + + private Convertor<File, SVNInfo> createInfoGetter(final SVNRevision revision) { + return new Convertor<File, SVNInfo>() { @Override public SVNInfo convert(File o) { try { @@ -181,7 +226,7 @@ public class SvnCommandLineStatusClient implements SvnStatusClientI { throw new SvnExceptionWrapper(e); } } - }); + }; } public static SvnStatusHandler.ExternalDataCallback createStatusCallback(final ISVNStatusHandler handler, diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineUpdateClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineUpdateClient.java index c0e9c8e79fa2..e0834c2f116f 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineUpdateClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineUpdateClient.java @@ -24,7 +24,6 @@ import com.intellij.util.ArrayUtil; import org.jetbrains.idea.svn.SvnApplicationSettings; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.checkin.IdeaSvnkitBasedAuthenticationCallback; -import org.jetbrains.idea.svn.config.SvnBindException; import org.jetbrains.idea.svn.portable.SvnSvnkitUpdateClient; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.wc.*; @@ -49,9 +48,9 @@ public class SvnCommandLineUpdateClient extends SvnSvnkitUpdateClient { private final VirtualFile myCommonAncestor; private boolean myIgnoreExternals; - public SvnCommandLineUpdateClient(final Project project, VirtualFile commonAncestor) { - super(SvnVcs.getInstance(project).createUpdateClient()); - myProject = project; + public SvnCommandLineUpdateClient(final SvnVcs vcs, VirtualFile commonAncestor) { + super(vcs.createUpdateClient()); + myProject = vcs.getProject(); myCommonAncestor = commonAncestor; } @@ -88,35 +87,7 @@ public class SvnCommandLineUpdateClient extends SvnSvnkitUpdateClient { File base = myCommonAncestor == null ? paths[0] : new File(myCommonAncestor.getPath()); base = base.isDirectory() ? base : base.getParentFile(); - final List<String> parameters = new ArrayList<String>(); - if (revision != null && ! SVNRevision.UNDEFINED.equals(revision) && ! SVNRevision.WORKING.equals(revision)) { - parameters.add("-r"); - parameters.add(revision.toString()); - } - // unknown depth is not used any more for 1.7 -> why? - if (depth != null && ! SVNDepth.UNKNOWN.equals(depth)) { - parameters.add("--depth"); - parameters.add(depth.toString()); - } - if (allowUnversionedObstructions) { - parameters.add("--force"); - } - if (depthIsSticky && depth != null) {// !!! not sure, but not used - parameters.add("--set-depth"); - parameters.add(depth.toString()); - } - if (makeParents) { - parameters.add("--parents"); - } - if (myIgnoreExternals) { - parameters.add("--ignore-externals"); - } - parameters.add("--accept"); - parameters.add("postpone"); - - for (File path : paths) { - parameters.add(path.getPath()); - } + final List<String> parameters = prepareParameters(paths, revision, depth, allowUnversionedObstructions, depthIsSticky, makeParents); final AtomicReference<SVNException> excRef = new AtomicReference<SVNException>(); final ISVNEventHandler handler = getEventHandler(); @@ -166,7 +137,7 @@ public class SvnCommandLineUpdateClient extends SvnSvnkitUpdateClient { } }; SvnLineCommand.runWithAuthenticationAttempt(SvnApplicationSettings.getInstance().getCommandLinePath(), - base, SvnCommandName.up, listener, + base, info.getURL(), SvnCommandName.up, listener, new IdeaSvnkitBasedAuthenticationCallback(SvnVcs.getInstance(myProject)), ArrayUtil.toStringArray(parameters)); } @@ -180,6 +151,29 @@ public class SvnCommandLineUpdateClient extends SvnSvnkitUpdateClient { return updatedToRevision.get(); } + private List<String> prepareParameters(File[] paths, + SVNRevision revision, + SVNDepth depth, + boolean allowUnversionedObstructions, + boolean depthIsSticky, boolean makeParents) { + List<String> parameters = new ArrayList<String>(); + + CommandUtil.put(parameters, revision); + CommandUtil.put(parameters, depth); + CommandUtil.put(parameters, allowUnversionedObstructions, "--force"); + if (depthIsSticky && depth != null) {// !!! not sure, but not used + parameters.add("--set-depth"); + parameters.add(depth.toString()); + } + CommandUtil.put(parameters, makeParents, "--parents"); + CommandUtil.put(parameters, myIgnoreExternals, "--ignore-externals"); + parameters.add("--accept"); + parameters.add("postpone"); + CommandUtil.put(parameters, paths); + + return parameters; + } + private void checkForException(final StringBuffer sbError) throws SVNException { if (sbError.length() == 0) return; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandName.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandName.java new file mode 100644 index 000000000000..c19ea283bbd1 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandName.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.commandLine; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 1/25/12 + * Time: 1:49 PM + */ +public enum SvnCommandName { + // TODO: temporary command for "more smooth" converting between simple commands and line commands + empty("", false), + version("--version", false), + info("info", false), + st("st", false), + up("up", true), + ci("commit", true), + cleanup("cleanup", true), + cat("cat", false), + add("add", true), + log("log", false), + revert("revert", true), + delete("delete", true), + copy("copy", true), + move("move", true), + resolve("resolve", true), + propget("propget", false), + blame("blame", false); + + private final String myName; + private final boolean myWriteable; + + private SvnCommandName(String name, boolean writeable) { + myName = name; + myWriteable = writeable; + } + + public String getName() { + return myName; + } + + public boolean isWriteable() { + return myWriteable; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommitRunner.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommitRunner.java new file mode 100644 index 000000000000..83f6ec156cf1 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommitRunner.java @@ -0,0 +1,209 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.commandLine; + +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.Convertor; +import org.apache.subversion.javahl.types.Revision; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.svn.core.SVNURL; + +import java.io.File; +import java.util.*; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/25/13 + * Time: 4:56 PM + */ +public class SvnCommitRunner { + private final String myExePath; + @Nullable private final AuthenticationCallback myAuthenticationCallback; + private static final Logger LOG = Logger.getInstance("org.jetbrains.idea.svn.commandLine.SvnCommitRunner"); + private SvnCommitRunner.CommandListener myCommandListener; + + public SvnCommitRunner(@NotNull String path, @Nullable CommitEventHandler handler, @Nullable AuthenticationCallback authenticationCallback) { + myExePath = path; + myCommandListener = new CommandListener(handler); + myAuthenticationCallback = authenticationCallback; + } + + public long commit(String[] paths, + String message, + int depth, + boolean noUnlock, + boolean keepChangelist, + String[] changelists, + Map revpropTable, Convertor<String[], SVNURL> urlProvider) throws VcsException { + if (paths.length == 0) return Revision.SVN_INVALID_REVNUM; + + final List<String> parameters = new ArrayList<String>(); + parameters.add("--depth"); + parameters.add(SvnBindUtil.getDepthName(depth)); + if (noUnlock) { + parameters.add("--no-unlock"); + } + if (keepChangelist) { + parameters.add("--keep-changelists"); + } + if (changelists != null && changelists.length > 0) { + SvnBindUtil.changelistsToCommand(changelists, parameters); + } + if (revpropTable != null && ! revpropTable.isEmpty()) { + final Set<Map.Entry<Object, Object>> set = revpropTable.entrySet(); + for (Map.Entry<Object, Object> entry : set) { + parameters.add("--with-revprop"); + parameters.add(entry.getKey() + "=" + entry.getValue()); + } + } + parameters.add("-m"); + parameters.add(message); + Arrays.sort(paths); + parameters.addAll(Arrays.asList(paths)); + + SvnLineCommand.runWithAuthenticationAttempt(myExePath, new File(paths[0]), urlProvider.convert(paths), SvnCommandName.ci, + myCommandListener, myAuthenticationCallback, ArrayUtil.toStringArray(parameters)); + myCommandListener.throwExceptionIfOccurred(); + + return myCommandListener.getCommittedRevision(); + } + + public static class CommandListener extends LineCommandListener { + @Nullable private final CommitEventHandler myHandler; + private SvnBindException myException; + private long myCommittedRevision = Revision.SVN_INVALID_REVNUM; + private File myBase; + + public CommandListener(@Nullable CommitEventHandler handler) { + myHandler = handler; + } + + public void throwExceptionIfOccurred() throws VcsException { + if (myException != null) { + throw myException; + } + } + + private long getCommittedRevision() { + return myCommittedRevision; + } + + @Override + public void baseDirectory(File file) { + myBase = file; + } + + @Override + public void onLineAvailable(String line, Key outputType) { + final String trim = line.trim(); + if (ProcessOutputTypes.STDOUT.equals(outputType)) { + try { + parseLine(trim); + } + catch (SvnBindException e) { + myException = e; + } + } + } + + private void parseLine(String line) throws SvnBindException { + if (StringUtil.isEmptyOrSpaces(line)) return; + if (line.startsWith(CommitEventType.transmittingDeltas.getText())) { + if (myHandler != null) { + myHandler.commitEvent(CommitEventType.transmittingDeltas, myBase); + } + return; + } + if (line.startsWith(CommitEventType.committedRevision.getText())) { + final String substring = line.substring(CommitEventType.committedRevision.getText().length()); + int cnt = 0; + while (StringUtil.isWhiteSpace(substring.charAt(cnt))) { + ++ cnt; + } + final StringBuilder num = new StringBuilder(); + while (Character.isDigit(substring.charAt(cnt))) { + num.append(substring.charAt(cnt)); + ++ cnt; + } + if (num.length() > 0) { + try { + myCommittedRevision = Long.parseLong(num.toString()); + if (myHandler != null) { + myHandler.committedRevision(myCommittedRevision); + } + } catch (NumberFormatException e) { + final String message = "Wrong committed revision number: " + num.toString() + ", string: " + line; + LOG.info(message, e); + throw new SvnBindException(message); + } + } else { + final String message = "Missing committed revision number: " + num.toString() + ", string: " + line; + LOG.info(message); + throw new SvnBindException(message); + } + } else { + if (myHandler == null) return; + final int idxSpace = line.indexOf(' '); + if (idxSpace == -1) { + LOG.info("Can not parse event type: " + line); + return; + } + final CommitEventType type = CommitEventType.create(line.substring(0, idxSpace)); + if (type == null) { + LOG.info("Can not parse event type: " + line); + return; + } + final File target = new File(myBase, new String(line.substring(idxSpace + 1).trim())); + myHandler.commitEvent(type, target); + } + } + } + +/*C:\TestProjects\sortedProjects\Subversion\local2\preRelease\mod2\src\com\test>sv + n st + D gggG + D gggG\Rrr.java + D gggG\and555.txt + D gggG\test.txt + A + gggGA + D + gggGA\Rrr.java + A + gggGA\RrrAA.java + D + gggGA\and.txt + M + gggGA\and555.txt + A gggGA\someNewFile.txt + + --- Changelist 'New changelistrwerwe': + A ddd.jpg + + C:\TestProjects\sortedProjects\Subversion\local2\preRelease\mod2\src\com\test>sv + n ci -m 123 + Adding ddd.jpg + Deleting gggG + Adding gggGA + Deleting gggGA\Rrr.java + Adding gggGA\RrrAA.java + Sending gggGA\and555.txt + Adding gggGA\someNewFile.txt + Transmitting file data .... + Committed revision 165.*/ +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnInfoHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnInfoHandler.java index 022130e4a9f6..d8dbb6c19f67 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnInfoHandler.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnInfoHandler.java @@ -18,6 +18,7 @@ package org.jetbrains.idea.svn.commandLine; import com.intellij.openapi.util.Getter; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; import org.tmatesoft.svn.core.SVNDepth; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNNodeKind; @@ -49,7 +50,7 @@ public class SvnInfoHandler extends DefaultHandler { public SvnInfoHandler(File base, final Consumer<SVNInfo> infoConsumer) { myBase = base; myInfoConsumer = infoConsumer; - myPending = new SvnInfoStructure(); + myPending = createPending(); myElementsMap = new HashMap<String, Getter<ElementHandlerBase>>(); fillElements(); myParseStack = new ArrayList<ElementHandlerBase>(); @@ -70,7 +71,14 @@ public class SvnInfoHandler extends DefaultHandler { myInfoConsumer.consume(info); } myResultsMap.put(info.getFile(), info); - myPending = new SvnInfoStructure(); + myPending = createPending(); + } + + private SvnInfoStructure createPending() { + SvnInfoStructure pending = new SvnInfoStructure(); + pending.myDepth = SVNDepth.INFINITY; + + return pending; } @Override @@ -90,16 +98,13 @@ public class SvnInfoHandler extends DefaultHandler { public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { assertSAX(! myParseStack.isEmpty()); ElementHandlerBase current = myParseStack.get(myParseStack.size() - 1); - if (mySb.length() > 0) { - current.characters(mySb.toString().trim(), myPending); - mySb.setLength(0); - } while (true) { final boolean createNewChild = current.startElement(uri, localName, qName, attributes); if (createNewChild) { assertSAX(myElementsMap.containsKey(qName)); final ElementHandlerBase newChild = myElementsMap.get(qName).get(); + newChild.setParent(current); newChild.updateInfo(attributes, myPending); myParseStack.add(newChild); return; @@ -116,6 +121,18 @@ public class SvnInfoHandler extends DefaultHandler { } @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + ElementHandlerBase current = myParseStack.get(myParseStack.size() - 1); + String value = mySb.toString().trim(); + + if (!StringUtil.isEmpty(value)) { + current.characters(value, myPending); + } + + mySb.setLength(0); + } + + @Override public void characters(char[] ch, int start, int length) throws SAXException { assertSAX(! myParseStack.isEmpty()); mySb.append(ch, start, length); @@ -252,6 +269,24 @@ public class SvnInfoHandler extends DefaultHandler { return new Url(); } }); + myElementsMap.put("relative-url", new Getter<ElementHandlerBase>() { + @Override + public ElementHandlerBase get() { + return new RelativeUrl(); + } + }); + myElementsMap.put("lock", new Getter<ElementHandlerBase>() { + @Override + public ElementHandlerBase get() { + return new Lock(); + } + }); + myElementsMap.put("created", new Getter<ElementHandlerBase>() { + @Override + public ElementHandlerBase get() { + return new Date(); + } + }); myElementsMap.put("uuid", new Getter<ElementHandlerBase>() { @Override public ElementHandlerBase get() { @@ -270,6 +305,18 @@ public class SvnInfoHandler extends DefaultHandler { return new WcInfo(); } }); + myElementsMap.put("moved-to", new Getter<ElementHandlerBase>() { + @Override + public ElementHandlerBase get() { + return new MovedPath(); + } + }); + myElementsMap.put("moved-from", new Getter<ElementHandlerBase>() { + @Override + public ElementHandlerBase get() { + return new MovedPath(); + } + }); myElementsMap.put("wcroot-abspath", new Getter<ElementHandlerBase>() { @Override public ElementHandlerBase get() { @@ -295,6 +342,12 @@ public class SvnInfoHandler extends DefaultHandler { @Override protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException { + // TODO: Currently information for conflict (not tree-conflict) available in svn 1.8 is not used + // TODO: And it also not suite well for SVNKit api + if (getParent() instanceof Conflict) { + return; + } + final String side = attributes.getValue("side"); if ("source-left".equals(side)) { final SvnInfoStructure.ConflictVersion conflictVersion = new SvnInfoStructure.ConflictVersion(); @@ -382,7 +435,7 @@ public class SvnInfoHandler extends DefaultHandler { @Override public void characters(String s, SvnInfoStructure structure) throws SAXException { - structure.myConflictWorking = s; + structure.myConflictNew = new File(s).getName(); } } @@ -397,7 +450,7 @@ public class SvnInfoHandler extends DefaultHandler { @Override public void characters(String s, SvnInfoStructure structure) throws SAXException { - structure.myConflictNew = s; + structure.myConflictWorking = new File(s).getName(); } } @@ -412,14 +465,13 @@ public class SvnInfoHandler extends DefaultHandler { @Override public void characters(String s, SvnInfoStructure structure) throws SAXException { - // todo path? or plus base - structure.myConflictOld = s; + structure.myConflictOld = new File(s).getName(); } } private static class Conflict extends ElementHandlerBase { private Conflict() { - super(new String[]{"prev-base-file","prev-wc-file","cur-base-file","prop-file"}, new String[]{}); + super(new String[]{"prev-base-file","prev-wc-file","cur-base-file","prop-file"}, new String[]{"version"}); } @Override @@ -498,6 +550,25 @@ public class SvnInfoHandler extends DefaultHandler { } } + /** + * "moved-from" and "moved-to" elements are represented by this class. + */ + private static class MovedPath extends ElementHandlerBase { + + private MovedPath() { + super(new String[]{}, new String[]{}); + } + + @Override + protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException { + } + + @Override + public void characters(String s, SvnInfoStructure structure) throws SAXException { + // TODO: is there some field to initialize from this value? + } + } + private static class TextUpdated extends ElementHandlerBase { private TextUpdated() { super(new String[]{}, new String[]{}); @@ -617,7 +688,7 @@ public class SvnInfoHandler extends DefaultHandler { private static class WcInfo extends ElementHandlerBase { private WcInfo() { super(new String[]{"wcroot-abspath", "schedule", "depth", "text-updated", "checksum", "changelist", "copy-from-url", - "copy-from-rev"}, new String[]{}); + "copy-from-rev", "moved-to", "moved-from"}, new String[]{}); } @Override @@ -698,11 +769,34 @@ public class SvnInfoHandler extends DefaultHandler { } } + private static class RelativeUrl extends Url{ + @Override + public void characters(String s, SvnInfoStructure structure) throws SAXException { + structure.relativeUrl = s; + } + } + + private static class Lock extends ElementHandlerBase { + private Lock() { + super(new String[]{"created"}, new String[]{}); + } + + @Override + protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException { + // TODO: + } + + @Override + public void characters(String s, SvnInfoStructure structure) throws SAXException { + // TODO: + } + } + private static class Entry extends ElementHandlerBase { private final File myBase; private Entry(final File base) { - super(new String[]{"url","repository","wc-info","commit","conflict","tree-conflict"}, new String[]{}); + super(new String[]{"url", "relative-url", "lock", "repository","wc-info","commit","conflict","tree-conflict"}, new String[]{}); myBase = base; } @@ -763,12 +857,22 @@ public class SvnInfoHandler extends DefaultHandler { private abstract static class ElementHandlerBase { private final Set<String> myAwaitedChildren; private final Set<String> myAwaitedChildrenMultiple; + private ElementHandlerBase parent; ElementHandlerBase(String[] awaitedChildren, String[] awaitedChildrenMultiple) { myAwaitedChildren = new HashSet<String>(Arrays.asList(awaitedChildren)); myAwaitedChildrenMultiple = new HashSet<String>(Arrays.asList(awaitedChildrenMultiple)); } + @NotNull + public ElementHandlerBase getParent() { + return parent; + } + + public void setParent(@NotNull ElementHandlerBase parent) { + this.parent = parent; + } + protected abstract void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException; public boolean startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnInfoStructure.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnInfoStructure.java index 0292b7d93614..1aa5248c1939 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnInfoStructure.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnInfoStructure.java @@ -34,6 +34,7 @@ import java.util.Date; */ public class SvnInfoStructure { public File myFile; + public String relativeUrl; public SVNURL myUrl; public SVNURL myRootURL; public long myRevision; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnLineCommand.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnLineCommand.java new file mode 100644 index 000000000000..c5e3b9e6861c --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnLineCommand.java @@ -0,0 +1,605 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.commandLine; + +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.LineHandlerHelper; +import com.intellij.openapi.vcs.LineProcessEventListener; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.util.ArrayUtil; +import com.intellij.util.EventDispatcher; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.SvnUtil; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; +import org.tmatesoft.svn.core.auth.SVNAuthentication; +import org.tmatesoft.svn.core.auth.SVNPasswordAuthentication; +import org.tmatesoft.svn.core.auth.SVNSSLAuthentication; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 1/25/12 + * Time: 4:05 PM + * + * honestly stolen from GitLineHandler + */ +public class SvnLineCommand extends SvnCommand { + + public static final String AUTHENTICATION_REALM = "Authentication realm:"; + public static final String CERTIFICATE_ERROR = "Error validating server certificate for"; + public static final String PASSPHRASE_FOR = "Passphrase for"; + public static final String UNABLE_TO_CONNECT = "svn: E170001:"; + public static final String CANNOT_AUTHENTICATE_TO_PROXY = "Could not authenticate to proxy server"; + public static final String AUTHENTICATION_FAILED_MESSAGE = "Authentication failed"; + + private static final String INVALID_CREDENTIALS_FOR_SVN_PROTOCOL = "svn: E170001: Can't get"; + private static final String UNTRUSTED_SERVER_CERTIFICATE = "Server SSL certificate untrusted"; + private static final String ACCESS_TO_PREFIX = "Access to "; + private static final String FORBIDDEN_STATUS = "forbidden"; + private static final String PASSWORD_STRING = "password"; + + private static final Pattern UNABLE_TO_CONNECT_TO_URL_PATTERN = Pattern.compile("Unable to connect to a repository at URL '(.*)'"); + + // kept for exact text + //public static final String CLIENT_CERTIFICATE_FILENAME = "Client certificate filename:"; + /** + * the partial line from stdout stream + */ + private final StringBuilder myStdoutLine = new StringBuilder(); + /** + * the partial line from stderr stream + */ + private final StringBuilder myStderrLine = new StringBuilder(); + private final EventDispatcher<LineProcessEventListener> myLineListeners; + private final AtomicReference<Integer> myExitCode; + private final StringBuffer myErr; + private final StringBuffer myStdOut; + + public SvnLineCommand(File workingDirectory, @NotNull SvnCommandName commandName, @NotNull @NonNls String exePath) { + this(workingDirectory, commandName, exePath, null); + } + + public SvnLineCommand(File workingDirectory, @NotNull SvnCommandName commandName, @NotNull @NonNls String exePath, File configDir) { + super(workingDirectory, commandName, exePath, configDir); + myLineListeners = EventDispatcher.create(LineProcessEventListener.class); + myExitCode = new AtomicReference<Integer>(); + myErr = new StringBuffer(); + myStdOut = new StringBuffer(); + } + + @Override + protected void processTerminated(int exitCode) { + // force newline + if (myStdoutLine.length() != 0) { + onTextAvailable("\n\r", ProcessOutputTypes.STDOUT); + } + else if (myStderrLine.length() != 0) { + onTextAvailable("\n\r", ProcessOutputTypes.STDERR); + } + } + + public static SvnLineCommand runWithAuthenticationAttempt(final String exePath, + final File firstFile, + final SVNURL url, + SvnCommandName commandName, + final LineCommandListener listener, + @Nullable AuthenticationCallback authenticationCallback, + String... parameters) throws SvnBindException { + File base = firstFile != null ? (firstFile.isDirectory() ? firstFile : firstFile.getParentFile()) : null; + base = SvnBindUtil.correctUpToExistingParent(base); + + listener.baseDirectory(base); + + File configDir = null; + + try { + // for IDEA proxy case + if (authenticationCallback != null) { + writeIdeaConfig2SubversionConfig(authenticationCallback, base); + configDir = authenticationCallback.getSpecialConfigDir(); + } + + while (true) { + final SvnLineCommand command = runCommand(exePath, commandName, listener, base, configDir, parameters); + final Integer exitCode = command.myExitCode.get(); + + // could be situations when exit code = 0, but there is info "warning" in error stream + // for instance, for "svn status" on non-working copy folder + if (exitCode != 0) { + if (command.myErr.length() > 0) { + // handle authentication + final String errText = command.myErr.toString().trim(); + if (authenticationCallback != null) { + final AuthCallbackCase callback = createCallback(errText, authenticationCallback, base, url); + if (callback != null) { + cleanup(exePath, command, base); + if (callback.getCredentials(errText)) { + if (authenticationCallback.getSpecialConfigDir() != null) { + configDir = authenticationCallback.getSpecialConfigDir(); + } + parameters = updateParameters(callback, parameters); + continue; + } + } + } + throw new SvnBindException(errText); + } else { + throw new SvnBindException("Svn process exited with error code: " + exitCode); + } + } else if (command.myErr.length() > 0) { + LOG.info("Detected warning - " + command.myErr); + } + return command; + } + } finally { + if (authenticationCallback != null) { + authenticationCallback.reset(); + } + } + } + + private static String[] updateParameters(AuthCallbackCase callback, String[] parameters) { + List<String> p = new ArrayList<String>(Arrays.asList(parameters)); + + callback.updateParameters(p); + return ArrayUtil.toStringArray(p); + } + + private static void writeIdeaConfig2SubversionConfig(@NotNull AuthenticationCallback authenticationCallback, @NotNull File base) throws SvnBindException { + if (authenticationCallback.haveDataForTmpConfig()) { + try { + if (! authenticationCallback.persistDataToTmpConfig(base)) { + throw new SvnBindException("Can not persist " + ApplicationNamesInfo.getInstance().getProductName() + + " HTTP proxy information into tmp config directory"); + } + } + catch (IOException e) { + throw new SvnBindException(e); + } + catch (URISyntaxException e) { + throw new SvnBindException(e); + } + assert authenticationCallback.getSpecialConfigDir() != null; + } + } + + private static AuthCallbackCase createCallback(final String errText, final AuthenticationCallback callback, final File base, final SVNURL url) { + if (errText.startsWith(CERTIFICATE_ERROR)) { + return new CertificateCallbackCase(callback, base); + } + if (errText.startsWith(AUTHENTICATION_REALM)) { + return new CredentialsCallback(callback, base); + } + if (errText.startsWith(PASSPHRASE_FOR)) { + return new PassphraseCallback(callback, base); + } + if (errText.startsWith(UNABLE_TO_CONNECT) && errText.contains(CANNOT_AUTHENTICATE_TO_PROXY)) { + return new ProxyCallback(callback, base); + } + // http/https protocol invalid credentials + if (errText.contains(AUTHENTICATION_FAILED_MESSAGE)) { + return new UsernamePasswordCallback(callback, base, url); + } + // messages could be "Can't get password", "Can't get username or password" + if (errText.contains(INVALID_CREDENTIALS_FOR_SVN_PROTOCOL) && errText.contains(PASSWORD_STRING)) { + // svn protocol invalid credentials + return new UsernamePasswordCallback(callback, base, url); + } + // https one-way protocol untrusted server certificate + if (errText.contains(UNTRUSTED_SERVER_CERTIFICATE)) { + return new CertificateCallbackCase(callback, base); + } + // https two-way protocol invalid client certificate + if (errText.contains(ACCESS_TO_PREFIX) && errText.contains(FORBIDDEN_STATUS)) { + return new TwoWaySslCallback(callback, base, url); + } + return null; + } + + // Special callback for svn 1.8 credentials request as --non-interactive does not return + // authentication realm (just url) - so we could not create temp cache + private static class UsernamePasswordCallback extends AuthCallbackCase { + protected SVNAuthentication myAuthentication; + protected SVNURL myUrl; + + protected UsernamePasswordCallback(AuthenticationCallback callback, File base, SVNURL url) { + super(callback, base); + myUrl = url; + } + + @Override + boolean getCredentials(String errText) throws SvnBindException { + myAuthentication = myAuthenticationCallback.requestCredentials(myUrl != null ? myUrl : parseUrlFromError(errText), + getType()); + + return myAuthentication != null; + } + + public String getType() { + return ISVNAuthenticationManager.PASSWORD; + } + + @Override + public void updateParameters(List<String> parameters) { + if (myAuthentication instanceof SVNPasswordAuthentication) { + SVNPasswordAuthentication auth = (SVNPasswordAuthentication)myAuthentication; + + parameters.add("--username"); + parameters.add(auth.getUserName()); + parameters.add("--password"); + parameters.add(auth.getPassword()); + if (!auth.isStorageAllowed()) { + parameters.add("--no-auth-cache"); + } + } + } + + private SVNURL parseUrlFromError(String errorText) { + Matcher matcher = UNABLE_TO_CONNECT_TO_URL_PATTERN.matcher(errorText); + String urlValue = null; + + if (matcher.find()) { + urlValue = matcher.group(1); + } + + return urlValue != null ? parseUrl(urlValue) : null; + } + + private SVNURL parseUrl(String urlValue) { + try { + return SVNURL.parseURIEncoded(urlValue); + } + catch (SVNException e) { + return null; + } + } + } + + private static class ProxyCallback extends AuthCallbackCase { + protected ProxyCallback(AuthenticationCallback callback, File base) { + super(callback, base); + } + + @Override + boolean getCredentials(String errText) throws SvnBindException { + return myAuthenticationCallback.askProxyCredentials(myBase); + } + } + + private static class CredentialsCallback extends AuthCallbackCase { + protected CredentialsCallback(AuthenticationCallback callback, File base) { + super(callback, base); + } + + @Override + boolean getCredentials(String errText) throws SvnBindException { + final String realm = + errText.startsWith(AUTHENTICATION_REALM) ? cutFirstLine(errText).substring(AUTHENTICATION_REALM.length()).trim() : null; + final boolean isPassword = StringUtil.containsIgnoreCase(errText, "password"); + if (myTried) { + myAuthenticationCallback.clearPassiveCredentials(realm, myBase, isPassword); + } + myTried = true; + if (myAuthenticationCallback.authenticateFor(realm, myBase, myAuthenticationCallback.getSpecialConfigDir() != null, isPassword)) { + return true; + } + throw new SvnBindException("Authentication canceled for realm: " + realm); + } + } + + private static String cutFirstLine(final String text) { + final int idx = text.indexOf('\n'); + if (idx == -1) return text; + return text.substring(0, idx); + } + + private static class CertificateCallbackCase extends AuthCallbackCase { + private boolean accepted; + + private CertificateCallbackCase(AuthenticationCallback callback, File base) { + super(callback, base); + } + + @Override + public boolean getCredentials(final String errText) throws SvnBindException { + // parse realm from error text + String realm = errText; + final int idx1 = realm.indexOf('\''); + if (idx1 == -1) { + throw new SvnBindException("Can not detect authentication realm name: " + errText); + } + final int idx2 = realm.indexOf('\'', idx1 + 1); + if (idx2== -1) { + throw new SvnBindException("Can not detect authentication realm name: " + errText); + } + realm = realm.substring(idx1 + 1, idx2); + if (! myTried && myAuthenticationCallback.acceptSSLServerCertificate(myBase, realm)) { + accepted = true; + myTried = true; + return true; + } + throw new SvnBindException("Server SSL certificate rejected"); + } + + @Override + public void updateParameters(List<String> parameters) { + if (accepted) { + parameters.add("--trust-server-cert"); + } + } + } + + private static class TwoWaySslCallback extends UsernamePasswordCallback { + + protected TwoWaySslCallback(AuthenticationCallback callback, File base, SVNURL url) { + super(callback, base, url); + } + + @Override + public String getType() { + return ISVNAuthenticationManager.SSL; + } + + @Override + public void updateParameters(List<String> parameters) { + if (myAuthentication instanceof SVNSSLAuthentication) { + SVNSSLAuthentication auth = (SVNSSLAuthentication)myAuthentication; + + // TODO: Seems that config option should be specified for concrete server and not for global group. + // as in that case it could be overriden by settings in config file + parameters.add("--config-option"); + parameters.add("servers:global:ssl-client-cert-file=" + auth.getCertificatePath()); + parameters.add("--config-option"); + parameters.add("servers:global:ssl-client-cert-password=" + auth.getPassword()); + if (!auth.isStorageAllowed()) { + parameters.add("--no-auth-cache"); + } + } + } + } + + private static abstract class AuthCallbackCase { + protected boolean myTried = false; + protected final AuthenticationCallback myAuthenticationCallback; + protected final File myBase; + + protected AuthCallbackCase(AuthenticationCallback callback, final File base) { + myAuthenticationCallback = callback; + myBase = base; + } + + abstract boolean getCredentials(final String errText) throws SvnBindException; + + public void updateParameters(List<String> parameters) { + } + } + + private static void cleanup(String exePath, SvnCommand command, File base) throws SvnBindException { + // TODO: could be issues with fake "empty" command as it is not writable - but only read commands currently use "empty" command + // TODO: and "empty" command will be removed shortly + if (command.isManuallyDestroyed() && command.getCommandName().isWriteable()) { + File wcRoot = SvnUtil.getWorkingCopyRootNew(base); + if (wcRoot == null) { + throw new SvnBindException("Can not find working copy root for: " + base.getPath()); + } + + final SvnSimpleCommand cleanupCommand = new SvnSimpleCommand(wcRoot, SvnCommandName.cleanup, exePath); + try { + cleanupCommand.run(); + } + catch (VcsException e) { + throw new SvnBindException(e); + } + } + } + + /*svn: E170001: Commit failed (details follow): + svn: E170001: Unable to connect to a repository at URL 'htt../svn/secondRepo/local2/trunk/mod2/src/com/test/gggGA' + svn: E170001: OPTIONS of 'htt.../svn/secondRepo/local2/trunk/mod2/src/com/test/gggGA': authorization failed: Could not authenticate to server: rejected Basic challenge (ht)*/ + private final static String ourAuthFailed = "authorization failed"; + private final static String ourAuthFailed2 = "Could not authenticate to server"; + + private static boolean isAuthenticationFailed(String s) { + return s.trim().startsWith(AUTHENTICATION_REALM); + //return s.contains(ourAuthFailed) && s.contains(ourAuthFailed2); + } + + private static SvnLineCommand runCommand(String exePath, + SvnCommandName commandName, + final LineCommandListener listener, + File base, File configDir, + String... parameters) throws SvnBindException { + final AtomicBoolean errorReceived = new AtomicBoolean(false); + final SvnLineCommand command = new SvnLineCommand(base, commandName, exePath, configDir) { + int myErrCnt = 0; + + @Override + protected void onTextAvailable(String text, Key outputType) { + + // we won't stop right now if got "authentication realm" -> since we want to get "password" string (that would mean password is expected + // or certificate maybe is expected + // but for certificate passphrase we get just "Passphrase for ..." - one line without line feed + + // for client certificate (when no path in servers file) we get + // Authentication realm: <text> + // Client certificate filename: + if (ProcessOutputTypes.STDERR.equals(outputType)) { + ++ myErrCnt; + final String trim = text.trim(); + // should end in 1 second + errorReceived.set(true); + if (trim.startsWith(UNABLE_TO_CONNECT)) { + // wait for 3 lines of text then + if (myErrCnt >= 3) { + destroyProcess(); + } + } else if (trim.startsWith(PASSPHRASE_FOR) || myErrCnt >= 2) { + destroyProcess(); + } + } + super.onTextAvailable(text, outputType); + } + }; + + //command.addParameters("--non-interactive"); + command.addParameters(parameters); + final AtomicReference<Throwable> exceptionRef = new AtomicReference<Throwable>(); + // several threads + command.addLineListener(new LineProcessEventListener() { + @Override + public void onLineAvailable(String line, Key outputType) { + if (ProcessOutputTypes.STDOUT.equals(outputType)) { + command.myStdOut.append(line); + } + + if (SvnCommand.LOG.isDebugEnabled()) { + SvnCommand.LOG.debug("==> " + line); + } + if (ApplicationManager.getApplication().isUnitTestMode()) { + System.out.println("==> " + line); + } + listener.onLineAvailable(line, outputType); + if (listener.isCanceled()) { + command.destroyProcess(); + return; + } + if (ProcessOutputTypes.STDERR.equals(outputType)) { + if (command.myErr.length() > 0) { + command.myErr.append('\n'); + } + command.myErr.append(line); + } + } + + @Override + public void processTerminated(int exitCode) { + listener.processTerminated(exitCode); + command.myExitCode.set(exitCode); + } + + @Override + public void startFailed(Throwable exception) { + listener.startFailed(exception); + exceptionRef.set(exception); + } + }); + command.start(); + boolean finished; + do { + finished = command.waitFor(500); + if (!finished && errorReceived.get()) { + command.waitFor(1000); + command.destroyProcess(); + break; + } + } + while (!finished); + + if (exceptionRef.get() != null) { + throw new SvnBindException(exceptionRef.get()); + } + return command; + } + + @Override + protected void onTextAvailable(String text, Key outputType) { + Iterator<String> lines = LineHandlerHelper.splitText(text).iterator(); + if (ProcessOutputTypes.STDOUT == outputType) { + notifyLines(outputType, lines, myStdoutLine); + } + else if (ProcessOutputTypes.STDERR == outputType) { + notifyLines(outputType, lines, myStderrLine); + } + } + + private void notifyLines(final Key outputType, final Iterator<String> lines, final StringBuilder lineBuilder) { + if (!lines.hasNext()) return; + if (lineBuilder.length() > 0) { + lineBuilder.append(lines.next()); + if (lines.hasNext()) { + // line is complete + final String line = lineBuilder.toString(); + notifyLine(line, outputType); + lineBuilder.setLength(0); + } + } + while (true) { + String line = null; + if (lines.hasNext()) { + line = lines.next(); + } + + if (lines.hasNext()) { + notifyLine(line, outputType); + } + else { + if (line != null && line.length() > 0) { + lineBuilder.append(line); + } + break; + } + } + } + + private void notifyLine(final String line, final Key outputType) { + String trimmed = LineHandlerHelper.trimLineSeparator(line); + myLineListeners.getMulticaster().onLineAvailable(trimmed, outputType); + } + + public void addLineListener(LineProcessEventListener listener) { + myLineListeners.addListener(listener); + super.addListener(listener); + } + + private static class PassphraseCallback extends AuthCallbackCase { + public PassphraseCallback(AuthenticationCallback callback, File base) { + super(callback, base); + } + + @Override + boolean getCredentials(String errText) throws SvnBindException { + // try to get from file + /*if (myTried) { + myAuthenticationCallback.clearPassiveCredentials(null, myBase); + }*/ + myTried = true; + if (myAuthenticationCallback.authenticateFor(null, myBase, myAuthenticationCallback.getSpecialConfigDir() != null, false)) { + return true; + } + throw new SvnBindException("Authentication canceled for : " + errText.substring(PASSPHRASE_FOR.length())); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnSimpleCommand.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnSimpleCommand.java new file mode 100644 index 000000000000..a088da0334a3 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnSimpleCommand.java @@ -0,0 +1,110 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.commandLine; + +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.vcs.ProcessEventListener; +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 1/25/12 + * Time: 4:04 PM + */ +public class SvnSimpleCommand extends SvnCommand { + private final StringBuilder myStderr; + private final StringBuilder myStdout; + private VcsException myException; + private final Object myDataLock; + + public SvnSimpleCommand(File workingDirectory, @NotNull SvnCommandName commandName, @NotNull @NonNls String exePath) { + super(workingDirectory, commandName, exePath); + + myDataLock = new Object(); + myStderr = new StringBuilder(); + myStdout = new StringBuilder(); + } + + @Override + protected void processTerminated(int exitCode) { + // + } + + @Override + protected void onTextAvailable(String text, Key outputType) { + synchronized (myDataLock) { + if (ProcessOutputTypes.STDOUT.equals(outputType)) { + myStdout.append(text); + } else if (ProcessOutputTypes.STDERR.equals(outputType)) { + myStderr.append(text); + } + } + } + + public StringBuilder getStderr() { + synchronized (myDataLock) { + return myStderr; + } + } + + public StringBuilder getStdout() { + synchronized (myDataLock) { + return myStdout; + } + } + + public String run() throws VcsException { + addListener(new ProcessEventListener() { + @Override + public void processTerminated(int exitCode) { + } + + @Override + public void startFailed(Throwable exception) { + synchronized (myDataLock) { + myException = new VcsException("Process failed to start (" + myCommandLine.getCommandLineString() + "): " + exception.toString(), exception); + } + } + }); + start(); + if (isStarted()) {//if wasn't started, exception is stored into a field, don't wait for process + waitFor(-1); + } + + synchronized (myDataLock) { + if (myException != null) throw myException; + final int code = getExitCode(); + if (code == 0) { + String result = myStdout.toString(); + + LOG.debug(result); + + return result; + } else { + final String msg = new StringBuilder("Svn process exited with error code: ").append(code).append("\n") + .append("stderr: ").append(myStderr.toString()).append("\nstdout: ").append(getStdout().toString()) + .append("\nCommand was: ").append(myCommandLine.getCommandLineString()).toString(); + throw new VcsException(msg); + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/UpdateOutputLineConverter.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/UpdateOutputLineConverter.java index 6536646374bd..8700c363f460 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/UpdateOutputLineConverter.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/UpdateOutputLineConverter.java @@ -62,6 +62,8 @@ public class UpdateOutputLineConverter { } public SVNEvent convert(final String line) { + // TODO: Add direct processing of "Summary of conflicts" lines at the end of "svn update" output (if there are conflicts). + // TODO: Now it works ok because parseNormalLine could not determine necessary statuses from that and further lines if (StringUtil.isEmptyOrSpaces(line)) return null; if (line.startsWith(UPDATING)) { @@ -103,9 +105,9 @@ public class UpdateOutputLineConverter { if (line.length() < 5) return null; final char first = line.charAt(0); if (' ' != first && ! ourActions.contains(first)) return null; - final SVNStatusType contentsStatus = getStatusType(first); + final SVNStatusType contentsStatus = CommandUtil.getStatusType(first); final char second = line.charAt(1); - final SVNStatusType propertiesStatus = getStatusType(second); + final SVNStatusType propertiesStatus = CommandUtil.getStatusType(second); final char lock = line.charAt(2); // dont know what to do with stolen lock info if (' ' != lock && 'B' != lock) return null; final char treeConflict = line.charAt(3); @@ -140,28 +142,6 @@ public class UpdateOutputLineConverter { null, action, expectedAction, null, null, null, null, null); } - private SVNStatusType getStatusType(char first) { - final SVNStatusType contentsStatus; - if ('A' == first) { - contentsStatus = SVNStatusType.STATUS_ADDED; - } else if ('D' == first) { - contentsStatus = SVNStatusType.STATUS_DELETED; - } else if ('U' == first) { - contentsStatus = SVNStatusType.CHANGED; - } else if ('C' == first) { - contentsStatus = SVNStatusType.CONFLICTED; - } else if ('G' == first) { - contentsStatus = SVNStatusType.MERGED; - } else if ('R' == first) { - contentsStatus = SVNStatusType.STATUS_REPLACED; - } else if ('E' == first) { - contentsStatus = SVNStatusType.STATUS_OBSTRUCTED; - } else { - contentsStatus = SVNStatusType.STATUS_NORMAL; - } - return contentsStatus; - } - @Nullable private long matchAndGetRevision(final Pattern pattern, final String line) { final Matcher matcher = pattern.matcher(line); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/conflict/CmdConflictClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/conflict/CmdConflictClient.java new file mode 100644 index 000000000000..79614a24f554 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/conflict/CmdConflictClient.java @@ -0,0 +1,33 @@ +package org.jetbrains.idea.svn.conflict; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.jetbrains.idea.svn.commandLine.CommandUtil; +import org.jetbrains.idea.svn.commandLine.SvnCommandName; +import org.tmatesoft.svn.core.SVNDepth; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Konstantin Kolosovsky. + */ +public class CmdConflictClient extends BaseSvnClient implements ConflictClient { + + // TODO: Add possibility to resolve content conflicts separately from property conflicts. + @Override + public void resolve(@NotNull File path, boolean resolvePropertyConflicts) throws VcsException { + List<String> parameters = new ArrayList<String>(); + + CommandUtil.put(parameters, path); + CommandUtil.put(parameters, SVNDepth.EMPTY); + parameters.add("--accept"); + parameters.add("working"); + + // for now parsing of the output is not required as command is executed only for one file + // and will be either successful or exception will be thrown + CommandUtil.execute(myVcs, SvnCommandName.resolve, parameters, null); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/conflict/ConflictClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/conflict/ConflictClient.java new file mode 100644 index 000000000000..812fdef5621b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/conflict/ConflictClient.java @@ -0,0 +1,15 @@ +package org.jetbrains.idea.svn.conflict; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.api.SvnClient; + +import java.io.File; + +/** + * @author Konstantin Kolosovsky. + */ +public interface ConflictClient extends SvnClient { + + void resolve(@NotNull File path, boolean resolvePropertyConflicts) throws VcsException; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/conflict/SvnKitConflictClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/conflict/SvnKitConflictClient.java new file mode 100644 index 000000000000..33ee0f870c95 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/conflict/SvnKitConflictClient.java @@ -0,0 +1,25 @@ +package org.jetbrains.idea.svn.conflict; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.SVNConflictChoice; + +import java.io.File; + +/** + * @author Konstantin Kolosovsky. + */ +public class SvnKitConflictClient extends BaseSvnClient implements ConflictClient { + @Override + public void resolve(@NotNull File path, boolean resolvePropertyConflicts) throws VcsException { + try { + myVcs.createWCClient().doResolve(path, SVNDepth.EMPTY, true, resolvePropertyConflicts, SVNConflictChoice.MERGED); + } + catch (SVNException e) { + throw new VcsException(e); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/content/CmdContentClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/content/CmdContentClient.java new file mode 100644 index 000000000000..c822fe0a1f48 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/content/CmdContentClient.java @@ -0,0 +1,39 @@ +package org.jetbrains.idea.svn.content; + +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.impl.ContentRevisionCache; +import com.intellij.openapi.vfs.CharsetToolkit; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.jetbrains.idea.svn.commandLine.*; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc2.SvnTarget; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Konstantin Kolosovsky. + */ +public class CmdContentClient extends BaseSvnClient implements ContentClient { + + @Override + public byte[] getContent(@NotNull SvnTarget target, @Nullable SVNRevision revision, @Nullable SVNRevision pegRevision) + throws VcsException, FileTooBigRuntimeException { + // TODO: rewrite this to provide output as Stream + // TODO: rewrite without conversion from String to byte[] + // TODO: Also implement max size constraint like in SvnKitContentClient + List<String> parameters = new ArrayList<String>(); + CommandUtil.put(parameters, target.getPathOrUrlString(), pegRevision); + CommandUtil.put(parameters, revision); + + SvnCommand command = CommandUtil.execute(myVcs, SvnCommandName.cat, parameters, null); + + byte[] bytes = CharsetToolkit.getUtf8Bytes(command.getOutput()); + + ContentRevisionCache.checkContentsSize(target.getPathOrUrlString(), bytes.length); + + return bytes; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/content/ContentClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/content/ContentClient.java new file mode 100644 index 000000000000..3654d09c88bc --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/content/ContentClient.java @@ -0,0 +1,17 @@ +package org.jetbrains.idea.svn.content; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.SvnClient; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc2.SvnTarget; + +/** + * @author Konstantin Kolosovsky. + */ +public interface ContentClient extends SvnClient { + + byte[] getContent(@NotNull SvnTarget target, @Nullable SVNRevision revision, @Nullable SVNRevision pegRevision) + throws VcsException, FileTooBigRuntimeException; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/content/FileTooBigRuntimeException.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/content/FileTooBigRuntimeException.java new file mode 100644 index 000000000000..0a0e585069a6 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/content/FileTooBigRuntimeException.java @@ -0,0 +1,7 @@ +package org.jetbrains.idea.svn.content; + +/** + * @author Konstantin Kolosovsky. + */ +public class FileTooBigRuntimeException extends RuntimeException { +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/content/SvnKitContentClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/content/SvnKitContentClient.java new file mode 100644 index 000000000000..d5916970e320 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/content/SvnKitContentClient.java @@ -0,0 +1,61 @@ +package org.jetbrains.idea.svn.content; + +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.impl.ContentRevisionCache; +import com.intellij.vcsUtil.VcsUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; +import org.tmatesoft.svn.core.wc2.SvnTarget; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * @author Konstantin Kolosovsky. + */ +public class SvnKitContentClient extends BaseSvnClient implements ContentClient { + + @Override + public byte[] getContent(@NotNull SvnTarget target, @Nullable SVNRevision revision, @Nullable SVNRevision pegRevision) + throws VcsException, FileTooBigRuntimeException { + final int maxSize = VcsUtil.getMaxVcsLoadedFileSize(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream() { + @Override + public synchronized void write(int b) { + if (size() > maxSize) throw new FileTooBigRuntimeException(); + super.write(b); + } + + @Override + public synchronized void write(byte[] b, int off, int len) { + if (size() > maxSize) throw new FileTooBigRuntimeException(); + super.write(b, off, len); + } + + @Override + public synchronized void writeTo(OutputStream out) throws IOException { + if (size() > maxSize) throw new FileTooBigRuntimeException(); + super.writeTo(out); + } + }; + SVNWCClient wcClient = myVcs.createWCClient(); + try { + if (target.isURL()) { + wcClient.doGetFileContents(target.getURL(), pegRevision, revision, true, buffer); + } else { + wcClient.doGetFileContents(target.getFile(), pegRevision, revision, true, buffer); + } + ContentRevisionCache.checkContentsSize(target.getPathOrUrlString(), buffer.size()); + } catch (FileTooBigRuntimeException e) { + ContentRevisionCache.checkContentsSize(target.getPathOrUrlString(), buffer.size()); + } catch (SVNException e) { + throw new VcsException(e); + } + return buffer.toByteArray(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/copy/CmdCopyMoveClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/copy/CmdCopyMoveClient.java new file mode 100644 index 000000000000..eceef14d994d --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/copy/CmdCopyMoveClient.java @@ -0,0 +1,30 @@ +package org.jetbrains.idea.svn.copy; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.jetbrains.idea.svn.commandLine.CommandUtil; +import org.jetbrains.idea.svn.commandLine.SvnCommandName; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Konstantin Kolosovsky. + */ +public class CmdCopyMoveClient extends BaseSvnClient implements CopyMoveClient { + + @Override + public void copy(@NotNull File src, @NotNull File dst, boolean makeParents, boolean isMove) throws VcsException { + List<String> parameters = new ArrayList<String>(); + + CommandUtil.put(parameters, src); + CommandUtil.put(parameters, dst); + CommandUtil.put(parameters, makeParents, "--parents"); + + // for now parsing of the output is not required as command is executed only for one file + // and will be either successful or exception will be thrown + CommandUtil.execute(myVcs, isMove ? SvnCommandName.move : SvnCommandName.copy, parameters, null); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/copy/CopyMoveClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/copy/CopyMoveClient.java new file mode 100644 index 000000000000..ec48107c8223 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/copy/CopyMoveClient.java @@ -0,0 +1,15 @@ +package org.jetbrains.idea.svn.copy; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.api.SvnClient; + +import java.io.File; + +/** + * @author Konstantin Kolosovsky. + */ +public interface CopyMoveClient extends SvnClient { + + void copy(@NotNull File src, @NotNull File dst, boolean makeParents, boolean isMove) throws VcsException; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/copy/SvnKitCopyMoveClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/copy/SvnKitCopyMoveClient.java new file mode 100644 index 000000000000..ea84bc6f0816 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/copy/SvnKitCopyMoveClient.java @@ -0,0 +1,28 @@ +package org.jetbrains.idea.svn.copy; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.SVNCopySource; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.io.File; + +/** + * @author Konstantin Kolosovsky. + */ +public class SvnKitCopyMoveClient extends BaseSvnClient implements CopyMoveClient { + + @Override + public void copy(@NotNull File src, @NotNull File dst, boolean makeParents, boolean isMove) throws VcsException { + final SVNCopySource copySource = new SVNCopySource(isMove ? SVNRevision.UNDEFINED : SVNRevision.WORKING, SVNRevision.WORKING, src); + + try { + myVcs.createCopyClient().doCopy(new SVNCopySource[]{copySource}, dst, isMove, makeParents, true); + } + catch (SVNException e) { + throw new VcsException(e); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/delete/CmdDeleteClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/delete/CmdDeleteClient.java new file mode 100644 index 000000000000..5fc26acfb1d3 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/delete/CmdDeleteClient.java @@ -0,0 +1,29 @@ +package org.jetbrains.idea.svn.delete; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.jetbrains.idea.svn.commandLine.CommandUtil; +import org.jetbrains.idea.svn.commandLine.SvnCommandName; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Konstantin Kolosovsky. + */ +public class CmdDeleteClient extends BaseSvnClient implements DeleteClient { + + @Override + public void delete(@NotNull File path, boolean force) throws VcsException { + List<String> parameters = new ArrayList<String>(); + + CommandUtil.put(parameters, path); + CommandUtil.put(parameters, force, "--force"); + + // for now parsing of the output is not required as command is executed only for one file + // and will be either successful or exception will be thrown + CommandUtil.execute(myVcs, SvnCommandName.delete, parameters, null); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/delete/DeleteClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/delete/DeleteClient.java new file mode 100644 index 000000000000..23a1cf97cb31 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/delete/DeleteClient.java @@ -0,0 +1,15 @@ +package org.jetbrains.idea.svn.delete; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.api.SvnClient; + +import java.io.File; + +/** + * @author Konstantin Kolosovsky. + */ +public interface DeleteClient extends SvnClient { + + void delete(@NotNull File path, boolean force) throws VcsException; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/delete/SvnKitDeleteClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/delete/SvnKitDeleteClient.java new file mode 100644 index 000000000000..c97cfd2a44bb --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/delete/SvnKitDeleteClient.java @@ -0,0 +1,24 @@ +package org.jetbrains.idea.svn.delete; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.tmatesoft.svn.core.SVNException; + +import java.io.File; + +/** + * @author Konstantin Kolosovsky. + */ +public class SvnKitDeleteClient extends BaseSvnClient implements DeleteClient { + + @Override + public void delete(@NotNull File path, boolean force) throws VcsException { + try { + myVcs.createWCClient().doDelete(path, force, false); + } + catch (SVNException e) { + throw new VcsException(e); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CreateBranchOrTagDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CreateBranchOrTagDialog.java index 62fc918d8a92..e1ad9157ce7b 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CreateBranchOrTagDialog.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CreateBranchOrTagDialog.java @@ -263,17 +263,11 @@ public class CreateBranchOrTagDialog extends DialogWrapper { super.init(); SvnVcs vcs = SvnVcs.getInstance(myProject); String revStr = ""; - try { - SVNWCClient client = vcs.createWCClient(); - SVNInfo info = client.doInfo(mySrcFile, SVNRevision.UNDEFINED); - if (info != null) { - mySrcURL = info.getURL() == null ? null : info.getURL().toString(); - revStr = String.valueOf(info.getRevision()); - myURL = mySrcURL; - } - } - catch (SVNException e) { - // + SVNInfo info = vcs.getInfo(mySrcFile); + if (info != null) { + mySrcURL = info.getURL() == null ? null : info.getURL().toString(); + revStr = String.valueOf(info.getRevision()); + myURL = mySrcURL; } if (myURL == null) { return; @@ -354,15 +348,8 @@ public class CreateBranchOrTagDialog extends DialogWrapper { return true; } else if (myWorkingCopyRadioButton.isSelected()) { - String srcUrl; - try { - SVNWCClient client = SvnVcs.getInstance(myProject).createWCClient(); - SVNInfo info = client.doInfo(mySrcFile, SVNRevision.UNDEFINED); - srcUrl = info != null && info.getURL() != null ? info.getURL().toString() : null; - } - catch (SVNException e) { - srcUrl = null; - } + SVNInfo info = SvnVcs.getInstance(myProject).getInfo(mySrcFile); + String srcUrl = info != null && info.getURL() != null ? info.getURL().toString() : null; if (srcUrl == null) { myErrorLabel.setText(SvnBundle.message("create.branch.no.working.copy.error", myWorkingCopyField.getText())); return false; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/CmdHistoryClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/CmdHistoryClient.java new file mode 100644 index 000000000000..3948ddee57b7 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/CmdHistoryClient.java @@ -0,0 +1,223 @@ +package org.jetbrains.idea.svn.history; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.util.LineSeparator; +import com.intellij.util.containers.hash.HashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.jetbrains.idea.svn.commandLine.CommandUtil; +import org.jetbrains.idea.svn.commandLine.SvnCommandName; +import org.jetbrains.idea.svn.commandLine.SvnLineCommand; +import org.tmatesoft.svn.core.ISVNLogEntryHandler; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNLogEntry; +import org.tmatesoft.svn.core.SVNLogEntryPath; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.io.File; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Konstantin Kolosovsky. + */ +public class CmdHistoryClient extends BaseSvnClient implements HistoryClient { + + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.history.CmdHistoryClient"); + + @Override + public void doLog(@NotNull File path, + @NotNull SVNRevision startRevision, + @NotNull SVNRevision endRevision, + @Nullable SVNRevision pegRevision, + boolean stopOnCopy, + boolean discoverChangedPaths, + boolean includeMergedRevisions, + long limit, + @Nullable String[] revisionProperties, + @Nullable ISVNLogEntryHandler handler) throws VcsException { + // TODO: add revision properties parameter if necessary + // TODO: svn log command supports --xml option - could update parsing to use xml format + + // TODO: after merge remove setting includeMergedRevisions to false and update parsing + includeMergedRevisions = false; + + List<String> parameters = + prepareCommand(path, startRevision, endRevision, pegRevision, stopOnCopy, discoverChangedPaths, includeMergedRevisions, limit); + + try { + SvnLineCommand command = CommandUtil.runSimple(SvnCommandName.log, myVcs, path, null, parameters); + // TODO: handler should be called in parallel with command execution, but this will be in other thread + // TODO: check if that is ok for current handler implementation + parseOutput(handler, command); + } + catch (SVNException e) { + throw new VcsException(e); + } + } + + private static void parseOutput(@Nullable ISVNLogEntryHandler handler, @NotNull SvnLineCommand command) + throws VcsException, SVNException { + Parser parser = new Parser(handler); + for (String line : StringUtil.splitByLines(command.getOutput(), false)) { + parser.onLine(line); + } + } + + private static List<String> prepareCommand(@NotNull File path, + @NotNull SVNRevision startRevision, + @NotNull SVNRevision endRevision, + @Nullable SVNRevision pegRevision, + boolean stopOnCopy, boolean discoverChangedPaths, boolean includeMergedRevisions, long limit) { + List<String> parameters = new ArrayList<String>(); + + CommandUtil.put(parameters, path, pegRevision); + parameters.add("--revision"); + parameters.add(startRevision + ":" + endRevision); + + CommandUtil.put(parameters, stopOnCopy, "--stop-on-copy"); + CommandUtil.put(parameters, discoverChangedPaths, "--verbose"); + CommandUtil.put(parameters, includeMergedRevisions, "--use-merge-history"); + if (limit > 0) { + parameters.add("--limit"); + parameters.add(String.valueOf(limit)); + } + + return parameters; + } + + private static class Parser { + private static final String REVISION = "\\s*r(\\d+)\\s*"; + private static final String AUTHOR = "\\s*([^|]*)\\s*"; + private static final String DATE = "\\s*([^|]*)\\s*"; + private static final String MESSAGE_LINES = "\\s*(\\d+).*"; + + private static final Pattern ENTRY_START = Pattern.compile("-+"); + private static final Pattern DETAILS = Pattern.compile(REVISION + "\\|" + AUTHOR + "\\|" + DATE + "\\|" + MESSAGE_LINES); + + private static final String STATUS = "\\s*(\\w)"; + private static final String PATH = "\\s*(.*?)\\s*"; + private static final String COPY_FROM_PATH = "(/[^:]*)"; + private static final String COPY_FROM_REVISION = "(\\d+)\\))\\s*"; + private static final String COPY_FROM_INFO = "((\\(from " + COPY_FROM_PATH + ":" + COPY_FROM_REVISION + ")?"; + private static final Pattern CHANGED_PATH = Pattern.compile(STATUS + PATH + COPY_FROM_INFO); + + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); + + ISVNLogEntryHandler handler; + + Entry entry; + boolean waitDetails; + boolean waitChangedPath; + boolean waitMessage; + + public Parser(@Nullable ISVNLogEntryHandler handler) { + this.handler = handler; + } + + public void onLine(@NotNull String line) throws VcsException, SVNException { + if (ENTRY_START.matcher(line).matches()) { + processEntryStart(); + } + else if (waitDetails) { + processDetails(line); + } + else if (waitMessage) { + processMessage(line); + } + else if (StringUtil.isEmpty(line.trim())) { + processChangedPathsFinished(); + } + else if (line.startsWith("Changed paths:")) { + processChangedPathsStarted(); + } + else if (waitChangedPath) { + processChangedPath(line); + } + else { + throw new VcsException("unknown state on line " + line); + } + } + + private void processChangedPath(@NotNull String line) throws VcsException { + Matcher matcher = CHANGED_PATH.matcher(line); + if (!matcher.matches()) { + throw new VcsException("changed path not found in " + line); + } + + String path = matcher.group(2); + char type = CommandUtil.getStatusChar(matcher.group(1)); + String copyPath = matcher.group(5); + long copyRevision = !StringUtil.isEmpty(matcher.group(6)) ? Long.valueOf(matcher.group(6)) : 0; + + entry.changedPaths.put(path, new SVNLogEntryPath(path, type, copyPath, copyRevision)); + } + + private void processChangedPathsStarted() { + waitChangedPath = true; + } + + private void processChangedPathsFinished() { + waitChangedPath = false; + waitMessage = true; + } + + private void processMessage(@NotNull String line) { + entry.message.append(line); + entry.message.append(LineSeparator.LF.getSeparatorString()); + } + + private void processDetails(@NotNull String line) throws VcsException { + Matcher matcher = DETAILS.matcher(line); + if (!matcher.matches()) { + throw new VcsException("details not found in " + line); + } + entry.revision = Long.valueOf(matcher.group(1)); + entry.author = matcher.group(2).trim(); + entry.date = tryGetDate(matcher.group(3)); + + waitDetails = false; + } + + private void processEntryStart() throws SVNException { + if (entry != null) { + handler.handleLogEntry(entry.toLogEntry()); + } + entry = new Entry(); + waitDetails = true; + waitMessage = false; + } + + private static Date tryGetDate(@NotNull String value) { + Date result = null; + try { + result = DATE_FORMAT.parse(value); + } + catch (ParseException e) { + LOG.debug(e); + } + return result; + } + + private static class Entry { + Map<String, SVNLogEntryPath> changedPaths = new HashMap<String, SVNLogEntryPath>(); + long revision; + String author; + Date date; + StringBuilder message = new StringBuilder(); + + private SVNLogEntry toLogEntry() { + return new SVNLogEntry(changedPaths, revision, author, date, message.toString()); + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/HistoryClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/HistoryClient.java new file mode 100644 index 000000000000..87e5e76556fa --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/HistoryClient.java @@ -0,0 +1,28 @@ +package org.jetbrains.idea.svn.history; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.SvnClient; +import org.tmatesoft.svn.core.ISVNLogEntryHandler; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.io.File; + +/** + * @author Konstantin Kolosovsky. + */ +public interface HistoryClient extends SvnClient { + + // TODO: Url is also supported as parameter in cmd - use Target class + void doLog(@NotNull File path, + @NotNull SVNRevision startRevision, + @NotNull SVNRevision endRevision, + @Nullable SVNRevision pegRevision, + boolean stopOnCopy, + boolean discoverChangedPaths, + boolean includeMergedRevisions, + long limit, + @Nullable String[] revisionProperties, + @Nullable ISVNLogEntryHandler handler) throws VcsException; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnCommittedChangesProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnCommittedChangesProvider.java index 7ef0b2f3d0b6..3552853a8479 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnCommittedChangesProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnCommittedChangesProvider.java @@ -518,9 +518,8 @@ public class SvnCommittedChangesProvider implements CachingCommittedChangesProvi revisionBefore = SVNRevision.create(revision); svnurl = SVNURL.parseURIEncoded(url); - final SVNWCClient client = myVcs.createWCClient(); - final SVNInfo info = client.doInfo(svnurl, SVNRevision.UNDEFINED, SVNRevision.HEAD); - targetInfo = client.doInfo(new File(file.getPath()), SVNRevision.UNDEFINED); + final SVNInfo info = myVcs.getInfo(svnurl, SVNRevision.HEAD); + targetInfo = myVcs.getInfo(new File(file.getPath())); if (info == null) { throw new VcsException("Can not get repository URL"); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnEditCommitMessageAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnEditCommitMessageAction.java index 5ca1150869ae..d67e29b598a0 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnEditCommitMessageAction.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnEditCommitMessageAction.java @@ -137,7 +137,7 @@ public class SvnEditCommitMessageAction extends AnAction { final String url = myLocation.getURL(); final SVNURL root; try { - root = SvnUtil.getRepositoryRoot(myVcs, SVNURL.parseURIEncoded(url), true); + root = SvnUtil.getRepositoryRoot(myVcs, SVNURL.parseURIEncoded(url)); if (root == null) { myException = new VcsException("Can not determine repository root for URL: " + url); return; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnFileRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnFileRevision.java index bcb40a034ac9..046e16c148a4 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnFileRevision.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnFileRevision.java @@ -33,6 +33,7 @@ import org.jetbrains.idea.svn.SvnUtil; import org.jetbrains.idea.svn.SvnVcs; import org.tmatesoft.svn.core.SVNLogEntry; import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc2.SvnTarget; import java.io.IOException; import java.nio.charset.Charset; @@ -211,7 +212,7 @@ public class SvnFileRevision implements VcsFileRevision { } try { - myContents = SvnUtil.getFileContents(myVCS, myURL, true, myRevision, myPegRevision); + myContents = SvnUtil.getFileContents(myVCS, SvnTarget.fromURL(SvnUtil.parseUrl(myURL)), myRevision, myPegRevision); } catch (VcsException e) { myException = e; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java index 4a2c8725a898..860b646947f5 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java @@ -42,7 +42,6 @@ import org.jetbrains.idea.svn.*; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; -import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.*; import org.tmatesoft.svn.util.SVNLogType; @@ -50,7 +49,6 @@ import javax.swing.*; import javax.swing.table.TableCellRenderer; import java.awt.*; import java.awt.event.MouseEvent; -import java.io.File; import java.nio.charset.Charset; import java.util.Collections; import java.util.Date; @@ -285,18 +283,9 @@ public class SvnHistoryProvider @Override protected void preliminary() throws SVNException { - SVNWCClient wcClient = myVcs.createWCClient(); - myInfo = wcClient.doInfo(new File(myFile.getIOFile().getAbsolutePath()), SVNRevision.UNDEFINED); - wcClient.setEventHandler(new ISVNEventHandler() { - public void handleEvent(SVNEvent event, double progress) throws SVNException { - } - - public void checkCancelled() throws SVNCancelException { - myPI.checkCanceled(); - } - }); + myInfo = myVcs.getInfo(myFile.getIOFile()); if (myInfo == null || myInfo.getRepositoryRootURL() == null) { - myException = new VcsException("File ''{0}'' is not under version control" + myFile.getIOFile()); + myException = new VcsException("File " + myFile.getPath() + " is not under version control"); return; } if (myInfo.getURL() == null) { @@ -319,14 +308,15 @@ public class SvnHistoryProvider myPI.setText2(SvnBundle.message("progress.text2.changes.establishing.connection", myUrl)); } final SVNRevision pegRevision = myInfo.getRevision(); - SVNLogClient client = myVcs.createLogClient(); try { - // a bug noticed when testing: we should pass "limit + 1" to get "limit" rows - client - .doLog(new File[]{new File(myFile.getIOFile().getAbsolutePath())}, - myFrom == null ? SVNRevision.HEAD : myFrom, myTo == null ? SVNRevision.create(1) : myTo, myPeg, - false, true, myShowMergeSources && mySupport15, myLimit + 1, null, - new MyLogEntryHandler(myVcs, myUrl, pegRevision, relativeUrl, createConsumerAdapter(myConsumer), repoRootURL, myFile.getCharset())); + myVcs.getFactory(myFile.getIOFile()).createHistoryClient().doLog( + myFile.getIOFile(), + myFrom == null ? SVNRevision.HEAD : myFrom, + myTo == null ? SVNRevision.create(1) : myTo, myPeg, + false, true, myShowMergeSources && mySupport15, myLimit + 1, null, + new MyLogEntryHandler(myVcs, myUrl, pegRevision, relativeUrl, + createConsumerAdapter(myConsumer), + repoRootURL, myFile.getCharset())); } catch (SVNCancelException e) { // @@ -383,7 +373,6 @@ public class SvnHistoryProvider } } - SVNWCClient wcClient = myVcs.createWCClient(); final SVNURL svnurl = SVNURL.parseURIEncoded(myUrl); SVNRevision operationalFrom = myFrom == null ? SVNRevision.HEAD : myFrom; final SVNURL rootURL = getRepositoryRoot(svnurl, myFrom); @@ -395,6 +384,7 @@ public class SvnHistoryProvider if (myUrl.startsWith(root)) { relativeUrl = myUrl.substring(root.length()); } + // TODO: Update this call to myVcs.getFactory.createHistoryClient SVNLogClient client = myVcs.createLogClient(); // a bug noticed when testing: we should pass "limit + 1" to get "limit" rows client.doLog(svnurl, new String[]{}, myPeg == null ? myFrom : myPeg, @@ -420,6 +410,7 @@ public class SvnHistoryProvider relativeUrl = myUrl.substring(root.length()); } + // TODO: Update this call to myVcs.getFactory.createHistoryClient SVNLogClient client = myVcs.createLogClient(); final RepositoryLogEntryHandler repositoryLogEntryHandler = @@ -437,36 +428,15 @@ public class SvnHistoryProvider } private SVNURL getRepositoryRoot(SVNURL svnurl, SVNRevision operationalFrom) throws SVNException { - final SVNRepository repository = myVcs.createRepository(svnurl); - final SVNURL root = repository.getRepositoryRoot(false); - if (root == null) { - return repository.getRepositoryRoot(true); - } - return root; - /*final SVNWCClient wcClient = myVcs.createWCClient(); - try { - final SVNInfo info; - info = wcClient.doInfo(svnurl, myPeg, operationalFrom); - return info.getRepositoryRootURL(); - } - catch (SVNException e) { - try { - final SVNInfo info; - info = wcClient.doInfo(svnurl, SVNRevision.UNDEFINED, SVNRevision.UNDEFINED); - return info.getRepositoryRootURL(); - } catch (SVNException e1) { - final SVNInfo info; - info = wcClient.doInfo(svnurl, SVNRevision.UNDEFINED, SVNRevision.HEAD); - return info.getRepositoryRootURL(); - } - }*/ + SVNInfo info = myVcs.getInfo(svnurl, SVNRevision.HEAD); + + return info != null ? info.getRepositoryRootURL() : null; } private boolean existsNow(SVNURL svnurl) { - final SVNWCClient wcClient = myVcs.createWCClient(); final SVNInfo info; try { - info = wcClient.doInfo(svnurl, SVNRevision.HEAD, SVNRevision.HEAD); + info = myVcs.getInfo(svnurl, SVNRevision.HEAD, SVNRevision.HEAD, null); } catch (SVNException e) { return false; @@ -493,6 +463,7 @@ public class SvnHistoryProvider protected final SvnPathThroughHistoryCorrection myLastPathCorrector; private final Charset myCharset; protected final ThrowableConsumer<VcsFileRevision, SVNException> myResult; + private final String myLastPath; private VcsFileRevision myPrevious; private final SVNRevision myPegRevision; protected final String myUrl; @@ -512,6 +483,7 @@ public class SvnHistoryProvider throws SVNException, VcsException { myVcs = vcs; myLastPathCorrector = new SvnPathThroughHistoryCorrection(lastPath); + myLastPath = lastPath; myCharset = charset; myIndicator = ProgressManager.getInstance().getProgressIndicator(); myResult = result; @@ -610,9 +582,9 @@ public class SvnHistoryProvider String author = logEntry.getAuthor(); String message = logEntry.getMessage(); SVNRevision rev = SVNRevision.create(logEntry.getRevision()); -// final SVNURL url = myRepositoryRoot.appendPath(myLastPath, true); - final SVNURL url = entryPath != null ? myRepositoryRoot.appendPath(entryPath.getPath(), true) : - myRepositoryRoot.appendPath(myLastPathCorrector.getBefore(), false); + final SVNURL url = myRepositoryRoot.appendPath(myLastPath, true); +// final SVNURL url = entryPath != null ? myRepositoryRoot.appendPath(entryPath.getPath(), true) : +// myRepositoryRoot.appendPath(myLastPathCorrector.getBefore(), false); return new SvnFileRevision(myVcs, myPegRevision, rev, url.toString(), author, date, message, copyPath, myCharset); } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistorySession.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistorySession.java index e30f8ca216cb..171b39cad623 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistorySession.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistorySession.java @@ -20,21 +20,19 @@ import com.intellij.openapi.vcs.history.*; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.SvnRevisionNumber; import org.jetbrains.idea.svn.SvnVcs; -import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.wc.SVNInfo; import org.tmatesoft.svn.core.wc.SVNRevision; -import org.tmatesoft.svn.core.wc.SVNWCClient; import java.io.File; import java.util.List; /** -* Created with IntelliJ IDEA. -* User: Irina.Chernushina -* Date: 4/27/12 -* Time: 12:24 PM -* To change this template use File | Settings | File Templates. -*/ + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 4/27/12 + * Time: 12:24 PM + * To change this template use File | Settings | File Templates. + */ public class SvnHistorySession extends VcsAbstractHistorySession { private final SvnVcs myVcs; private final FilePath myCommittedPath; @@ -71,19 +69,8 @@ public class SvnHistorySession extends VcsAbstractHistorySession { } public static VcsRevisionNumber getCurrentCommittedRevision(final SvnVcs vcs, final File file) { - try { - SVNWCClient wcClient = vcs.createWCClient(); - SVNInfo info = wcClient.doInfo(file, SVNRevision.UNDEFINED); - if (info != null) { - return new SvnRevisionNumber(info.getCommittedRevision()); - } - else { - return null; - } - } - catch (SVNException e) { - return null; - } + SVNInfo info = vcs.getInfo(file); + return info != null ? new SvnRevisionNumber(info.getCommittedRevision()) : null; } public FilePath getCommittedPath() { @@ -101,7 +88,8 @@ public class SvnHistorySession extends VcsAbstractHistorySession { @Override public VcsHistorySession copy() { - return new SvnHistorySession(myVcs, getRevisionList(), myCommittedPath, myHaveMergeSources, getCurrentRevisionNumber(), true, myHasLocalSource); + return new SvnHistorySession(myVcs, getRevisionList(), myCommittedPath, myHaveMergeSources, getCurrentRevisionNumber(), true, + myHasLocalSource); } @Override diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnKitHistoryClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnKitHistoryClient.java new file mode 100644 index 000000000000..6d412853400e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnKitHistoryClient.java @@ -0,0 +1,40 @@ +package org.jetbrains.idea.svn.history; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.tmatesoft.svn.core.ISVNLogEntryHandler; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.SVNLogClient; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.io.File; + +/** + * @author Konstantin Kolosovsky. + */ +public class SvnKitHistoryClient extends BaseSvnClient implements HistoryClient { + @Override + public void doLog(@NotNull File path, + @NotNull SVNRevision startRevision, + @NotNull SVNRevision endRevision, + @Nullable SVNRevision pegRevision, + boolean stopOnCopy, + boolean discoverChangedPaths, + boolean includeMergedRevisions, + long limit, + @Nullable String[] revisionProperties, + @Nullable ISVNLogEntryHandler handler) throws VcsException { + try { + // TODO: a bug noticed when testing: we should pass "limit + 1" to get "limit" rows + SVNLogClient client = myVcs.createLogClient(); + + client.doLog(new File[]{path}, startRevision, endRevision, pegRevision, stopOnCopy, discoverChangedPaths, includeMergedRevisions, + limit, revisionProperties, handler); + } + catch (SVNException e) { + throw new VcsException(e); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java index a668a54f953a..b01308f76c13 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java @@ -38,13 +38,10 @@ import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vcs.impl.ContentRevisionCache; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.idea.svn.SvnBundle; -import org.jetbrains.idea.svn.SvnRevisionNumber; -import org.jetbrains.idea.svn.SvnUtil; -import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.idea.svn.*; import org.tmatesoft.svn.core.SVNException; -import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc2.SvnTarget; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -108,7 +105,7 @@ public class SvnRepositoryContentRevision implements ContentRevision, MarkerVcsC else { loader.run(); } - final SVNException exception = loader.getException(); + final Exception exception = loader.getException(); if (exception != null) { throw new VcsException(exception); } @@ -148,7 +145,7 @@ public class SvnRepositoryContentRevision implements ContentRevision, MarkerVcsC private final String myPath; private final long myRevision; private final OutputStream myDst; - private SVNException myException; + private Exception myException; public ContentLoader(String path, OutputStream dst, long revision) { myPath = path; @@ -156,7 +153,7 @@ public class SvnRepositoryContentRevision implements ContentRevision, MarkerVcsC myRevision = revision; } - public SVNException getException() { + public Exception getException() { return myException; } @@ -166,16 +163,16 @@ public class SvnRepositoryContentRevision implements ContentRevision, MarkerVcsC progress.setText(SvnBundle.message("progress.text.loading.contents", myPath)); progress.setText2(SvnBundle.message("progress.text2.revision.information", myRevision)); } + try { - SVNRepository repository = myVcs.createRepository(getFullPath()); - try { - repository.getFile("", myRevision, null, myDst); - } - finally { - repository.closeSession(); - } + byte[] contents = SvnUtil.getFileContents(myVcs, SvnTarget.fromURL(SvnUtil.parseUrl(getFullPath())), SVNRevision.create(myRevision), + SVNRevision.UNDEFINED); + myDst.write(contents); } - catch (SVNException e) { + catch (VcsException e) { + myException = e; + } + catch (IOException e) { myException = e; } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/IntegratedSelectedOptionsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/IntegratedSelectedOptionsDialog.java index 78718a7a0264..bfb9f1ff22c3 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/IntegratedSelectedOptionsDialog.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/IntegratedSelectedOptionsDialog.java @@ -251,19 +251,10 @@ public class IntegratedSelectedOptionsDialog extends DialogWrapper { @Nullable private static SVNURL realTargetUrl(final SvnVcs vcs, final WorkingCopyInfo info, final String targetBranchUrl) { - final SVNWCClient client = vcs.createWCClient(); - try { - final SVNInfo svnInfo = client.doInfo(new File(info.getLocalPath()), SVNRevision.UNDEFINED); - final SVNURL svnurl = svnInfo.getURL(); + final SVNInfo svnInfo = vcs.getInfo(info.getLocalPath()); + final SVNURL svnurl = svnInfo != null ? svnInfo.getURL() : null; - if ((svnurl != null) && (svnurl.toString().startsWith(targetBranchUrl))) { - return svnurl; - } - } - catch (SVNException e) { - // tracked by return value - } - return null; + return (svnurl != null) && (svnurl.toString().startsWith(targetBranchUrl)) ? svnurl : null; } @Nullable diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/BranchInfo.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/BranchInfo.java index b6c6a898b85a..bc08fff144dc 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/BranchInfo.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/BranchInfo.java @@ -238,12 +238,7 @@ public class BranchInfo { } private SVNInfo getInfo(final File pathFile) { - try { - return myClient.doInfo(pathFile, SVNRevision.UNDEFINED); - } catch (SVNException e) { - // - } - return null; + return myVcs.getInfo(pathFile); } private SvnMergeInfoCache.MergeCheckResult checkPathGoingUp(final long revisionAsked, final long targetRevision, final String branchRootPath, diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/PortableStatus.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/PortableStatus.java index 184d5598f9fe..9799cf7ae4a4 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/PortableStatus.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/PortableStatus.java @@ -117,6 +117,7 @@ public class PortableStatus extends SVNStatus { return null; } }; + // TODO: Update working copy format detection setWorkingCopyFormat(WorkingCopyFormat.ONE_DOT_SEVEN.getFormat()); } @@ -242,6 +243,30 @@ public class PortableStatus extends SVNStatus { } @Override + public SVNURL getURL() { + SVNURL url = super.getURL(); + + if (url == null) { + SVNInfo info = initInfo(); + url = info != null ? info.getURL() : url; + } + + return url; + } + + @Override + public File getFile() { + File file = super.getFile(); + + if (file == null) { + SVNInfo info = initInfo(); + file = info != null ? info.getFile() : file; + } + + return file; + } + + @Override public SVNRevision getRevision() { final SVNRevision revision = super.getRevision(); if (revision != null && revision.isValid()) return revision; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/CmdPropertyClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/CmdPropertyClient.java new file mode 100644 index 000000000000..4720b33d9d37 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/CmdPropertyClient.java @@ -0,0 +1,65 @@ +package org.jetbrains.idea.svn.properties; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.jetbrains.idea.svn.commandLine.CommandUtil; +import org.jetbrains.idea.svn.commandLine.SvnCommand; +import org.jetbrains.idea.svn.commandLine.SvnCommandName; +import org.tmatesoft.svn.core.SVNPropertyValue; +import org.tmatesoft.svn.core.wc.SVNInfo; +import org.tmatesoft.svn.core.wc.SVNPropertyData; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Konstantin Kolosovsky. + */ +public class CmdPropertyClient extends BaseSvnClient implements PropertyClient { + + @Nullable + @Override + public SVNPropertyData getProperty(@NotNull File path, + @NotNull String property, + boolean revisionProperty, + @Nullable SVNRevision pegRevision, + @Nullable SVNRevision revision) + throws VcsException { + List<String> parameters = new ArrayList<String>(); + + parameters.add(property); + CommandUtil.put(parameters, path, pegRevision); + if (!revisionProperty) { + CommandUtil.put(parameters, revision); + } else { + parameters.add("--revprop"); + CommandUtil.put(parameters, resolveRevisionNumber(path, revision)); + } + + SvnCommand command = CommandUtil.execute(myVcs, SvnCommandName.propget, parameters, null); + + return new SVNPropertyData(property, SVNPropertyValue.create(StringUtil.nullize(command.getOutput())), null); + } + + private SVNRevision resolveRevisionNumber(@NotNull File path, @Nullable SVNRevision revision) throws VcsException { + long result = revision != null ? revision.getNumber() : -1; + + // base should be resolved manually - could not set revision to BASE to get revision property + if (SVNRevision.BASE.equals(revision)) { + SVNInfo info = myVcs.getInfo(path, SVNRevision.BASE); + + result = info != null ? info.getRevision().getNumber() : -1; + } + + if (result == -1) { + throw new VcsException("Could not determine revision number for file " + path + " and revision " + revision); + } + + return SVNRevision.create(result); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyClient.java new file mode 100644 index 000000000000..118a397eb23e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/PropertyClient.java @@ -0,0 +1,23 @@ +package org.jetbrains.idea.svn.properties; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.SvnClient; +import org.tmatesoft.svn.core.wc.SVNPropertyData; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.io.File; + +/** + * @author Konstantin Kolosovsky. + */ +public interface PropertyClient extends SvnClient { + + @Nullable + SVNPropertyData getProperty(@NotNull final File path, + @NotNull final String property, + boolean revisionProperty, + @Nullable SVNRevision pegRevision, + @Nullable SVNRevision revision) throws VcsException; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/SvnKitPropertyClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/SvnKitPropertyClient.java new file mode 100644 index 000000000000..1c3c23619074 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/properties/SvnKitPropertyClient.java @@ -0,0 +1,68 @@ +package org.jetbrains.idea.svn.properties; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.ISVNPropertyHandler; +import org.tmatesoft.svn.core.wc.SVNPropertyData; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; + +/** + * @author Konstantin Kolosovsky. + */ +public class SvnKitPropertyClient extends BaseSvnClient implements PropertyClient { + + @Nullable + @Override + public SVNPropertyData getProperty(@NotNull File path, + @NotNull String property, + boolean revisionProperty, + @Nullable SVNRevision pegRevision, + @Nullable SVNRevision revision) throws VcsException { + try { + if (!revisionProperty) { + return myVcs.createWCClient().doGetProperty(path, property, pegRevision, revision); + } else { + return getRevisionProperty(path, property, revision); + } + } + catch (SVNException e) { + throw new VcsException(e); + } + } + + private SVNPropertyData getRevisionProperty(@NotNull File path, @NotNull final String property, @Nullable SVNRevision revision) throws SVNException{ + final SVNWCClient client = myVcs.createWCClient(); + final SVNPropertyData[] result = new SVNPropertyData[1]; + + client.doGetRevisionProperty(path, null, revision, new ISVNPropertyHandler() { + @Override + public void handleProperty(File path, SVNPropertyData property) throws SVNException { + handle(property); + } + + @Override + public void handleProperty(SVNURL url, SVNPropertyData property) throws SVNException { + handle(property); + } + + @Override + public void handleProperty(long revision, SVNPropertyData property) throws SVNException { + handle(property); + } + + private void handle(@NotNull SVNPropertyData data) { + if (property.equals(data.getName())) { + result[0] = data; + } + } + }); + return result[0]; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/revert/CmdRevertClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/revert/CmdRevertClient.java new file mode 100644 index 000000000000..53c998f001a6 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/revert/CmdRevertClient.java @@ -0,0 +1,79 @@ +package org.jetbrains.idea.svn.revert; + +import com.intellij.openapi.vcs.VcsException; +import com.intellij.util.containers.Convertor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.jetbrains.idea.svn.api.FileStatusResultParser; +import org.jetbrains.idea.svn.commandLine.CommandUtil; +import org.jetbrains.idea.svn.commandLine.SvnCommandName; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; +import org.tmatesoft.svn.core.wc.SVNEvent; +import org.tmatesoft.svn.core.wc.SVNEventAction; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Konstantin Kolosovsky. + */ +public class CmdRevertClient extends BaseSvnClient implements RevertClient { + + private static final String STATUS = "\\s*(.+?)\\s*"; + private static final String PATH = "\\s*\'(.*?)\'\\s*"; + private static final String OPTIONAL_COMMENT = "(.*)"; + private static final Pattern CHANGED_PATH = Pattern.compile(STATUS + PATH + OPTIONAL_COMMENT); + + @Override + public void revert(@NotNull File[] paths, @Nullable SVNDepth depth, @Nullable ISVNEventHandler handler) throws VcsException { + List<String> parameters = prepareParameters(paths, depth); + + // TODO: handler should be called in parallel with command execution, but this will be in other thread + // TODO: check if that is ok for current handler implementation + // TODO: add possibility to invoke "handler.checkCancelled" - process should be killed + CommandUtil + .execute(myVcs, SvnCommandName.revert, parameters, new FileStatusResultParser(CHANGED_PATH, handler, new RevertStatusConvertor())); + } + + private static List<String> prepareParameters(File[] paths, SVNDepth depth) { + ArrayList<String> parameters = new ArrayList<String>(); + + CommandUtil.put(parameters, paths); + CommandUtil.put(parameters, depth); + + return parameters; + } + + private static class RevertStatusConvertor implements Convertor<Matcher, SVNEvent> { + + public SVNEvent convert(@NotNull Matcher matcher) { + String statusMessage = matcher.group(1); + String path = matcher.group(2); + + return new SVNEvent(new File(path), null, null, 0, null, null, null, null, createAction(statusMessage), null, null, null, null, null, + null); + } + + @Nullable + public static SVNEventAction createAction(@NotNull String code) { + SVNEventAction result = null; + + if ("Reverted".equals(code)) { + result = SVNEventAction.REVERT; + } + else if ("Failed to revert".equals(code)) { + result = SVNEventAction.FAILED_REVERT; + } + else if ("Skipped".equals(code)) { + result = SVNEventAction.SKIP; + } + + return result; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/revert/RevertClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/revert/RevertClient.java new file mode 100644 index 000000000000..053de373bc0a --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/revert/RevertClient.java @@ -0,0 +1,18 @@ +package org.jetbrains.idea.svn.revert; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.SvnClient; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; + +import java.io.File; + +/** + * @author Konstantin Kolosovsky. + */ +public interface RevertClient extends SvnClient { + + void revert(@NotNull File[] paths, @Nullable SVNDepth depth, @Nullable ISVNEventHandler handler) throws VcsException; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/revert/SvnKitRevertClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/revert/SvnKitRevertClient.java new file mode 100644 index 000000000000..17c725cf58f6 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/revert/SvnKitRevertClient.java @@ -0,0 +1,31 @@ +package org.jetbrains.idea.svn.revert; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.api.BaseSvnClient; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; + +/** + * @author Konstantin Kolosovsky. + */ +public class SvnKitRevertClient extends BaseSvnClient implements RevertClient { + + @Override + public void revert(@NotNull File[] paths, @Nullable SVNDepth depth, @Nullable ISVNEventHandler handler) throws VcsException { + SVNWCClient client = myVcs.createWCClient(); + + client.setEventHandler(handler); + try { + client.doRevert(paths, depth, null); + } + catch (SVNException e) { + throw new VcsException(e); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/rollback/SvnRollbackEnvironment.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/rollback/SvnRollbackEnvironment.java index 265d4ca5b9e5..4140ca75a717 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/rollback/SvnRollbackEnvironment.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/rollback/SvnRollbackEnvironment.java @@ -28,6 +28,7 @@ import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.*; +import org.jetbrains.idea.svn.revert.RevertClient; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.wc.*; @@ -74,8 +75,7 @@ public class SvnRollbackEnvironment extends DefaultRollbackEnvironment { checker.gather(changes); exceptions.addAll(checker.getExceptions()); - final SVNWCClient client = mySvnVcs.createWCClient(); - client.setEventHandler(new ISVNEventHandler() { + ISVNEventHandler revertHandler = new ISVNEventHandler() { public void handleEvent(SVNEvent event, double progress) { if (event.getAction() == SVNEventAction.REVERT) { final File file = event.getFile(); @@ -91,7 +91,7 @@ public class SvnRollbackEnvironment extends DefaultRollbackEnvironment { public void checkCancelled() { listener.checkCanceled(); } - }); + }; final List<CopiedAsideInfo> fromToModified = new ArrayList<CopiedAsideInfo>(); final MultiMap<File, SVNPropertyData> properties = new MultiMap<File, SVNPropertyData>(); @@ -99,7 +99,7 @@ public class SvnRollbackEnvironment extends DefaultRollbackEnvironment { // adds (deletes) // deletes (adds) // modifications - final Reverter reverter = new Reverter(client, exceptions); + final Reverter reverter = new Reverter(mySvnVcs.getFactory().createRevertClient(), revertHandler, exceptions); reverter.revert(checker.getForAdds(), true); reverter.revert(checker.getForDeletes(), true); final List<File> edits = checker.getForEdits(); @@ -256,24 +256,36 @@ public class SvnRollbackEnvironment extends DefaultRollbackEnvironment { } private static class Reverter { - private final SVNWCClient myClient; + private final RevertClient myClient; + private ISVNEventHandler myHandler; private final List<VcsException> myExceptions; - private Reverter(SVNWCClient client, List<VcsException> exceptions) { + private Reverter(RevertClient client, ISVNEventHandler handler, List<VcsException> exceptions) { myClient = client; + myHandler = handler; myExceptions = exceptions; } public void revert(final File[] files, final boolean recursive) { if (files.length == 0) return; try { - myClient.doRevert(files, recursive ? SVNDepth.INFINITY : SVNDepth.EMPTY, null); + myClient.revert(files, recursive ? SVNDepth.INFINITY : SVNDepth.EMPTY, myHandler); } - catch (SVNException e) { - if (e.getErrorMessage().getErrorCode() != SVNErrorCode.WC_NOT_DIRECTORY) { - // skip errors on unversioned resources. - myExceptions.add(new VcsException(e)); + catch (VcsException e) { + processRevertError(e); + } + } + + private void processRevertError(@NotNull VcsException e) { + if (e.getCause() instanceof SVNException) { + SVNException cause = (SVNException)e.getCause(); + + // skip errors on unversioned resources. + if (cause.getErrorMessage().getErrorCode() != SVNErrorCode.WC_NOT_DIRECTORY) { + myExceptions.add(e); } + } else { + myExceptions.add(e); } } } @@ -294,18 +306,17 @@ public class SvnRollbackEnvironment extends DefaultRollbackEnvironment { } private void revertFileOrDir(File file) throws SVNException, VcsException { - final SVNWCClient wcClient = mySvnVcs.createWCClient(); - SVNInfo info = wcClient.doInfo(file, SVNRevision.UNDEFINED); + SVNInfo info = mySvnVcs.getInfo(file); if (info != null) { if (info.getKind() == SVNNodeKind.FILE) { - wcClient.doRevert(file, false); + doRevert(file, false); } else { if (SVNProperty.SCHEDULE_ADD.equals(info.getSchedule())) { - wcClient.doRevert(file, true); + doRevert(file, true); } else { boolean under17Copy = isUnder17Copy(file, info); if (under17Copy) { - wcClient.doRevert(file, true); + doRevert(file, true); } else { // do update to restore missing directory. mySvnVcs.createUpdateClient().doUpdate(file, SVNRevision.HEAD, true); @@ -317,10 +328,16 @@ public class SvnRollbackEnvironment extends DefaultRollbackEnvironment { } } + private void doRevert(@NotNull File path, boolean recursive) throws VcsException { + mySvnVcs.getFactory(path).createRevertClient().revert(new File[]{path}, SVNDepth.fromRecurse(recursive), null); + } + private boolean isUnder17Copy(final File file, final SVNInfo info) throws VcsException { final RootsToWorkingCopies copies = mySvnVcs.getRootsToWorkingCopies(); WorkingCopy copy = copies.getMatchingCopy(info.getURL()); if (copy == null) { + // TODO: Why null could be here? + // TODO: Think we could just rewrite it with mySvnVcs.getWorkingCopyFormat(file) SVNStatus status = null; try { status = mySvnVcs.createStatusClient().doStatus(file, false); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/AbstractSvnUpdatePanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/AbstractSvnUpdatePanel.java index ecc3fa6b5204..d6bdf63cafc8 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/AbstractSvnUpdatePanel.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/AbstractSvnUpdatePanel.java @@ -109,17 +109,8 @@ public abstract class AbstractSvnUpdatePanel { @Nullable private SVNURL getUrlFor(@NotNull final FilePath root) { - try { - SVNWCClient wcClient = myVCS.createWCClient(); - final SVNInfo info = wcClient.doInfo(root.getIOFile(), SVNRevision.UNDEFINED); - if (info != null) { - return info.getURL(); - } - return null; - } - catch (SVNException e) { - return null; - } + final SVNInfo info = myVCS.getInfo(root.getIOFile()); + return info != null ? info.getURL() : null; } protected abstract JComponent getPanel(); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/AbstractUpdateIntegrateCrawler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/AbstractUpdateIntegrateCrawler.java index e9e7b0b5f897..826f7532b3a0 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/AbstractUpdateIntegrateCrawler.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/AbstractUpdateIntegrateCrawler.java @@ -59,7 +59,7 @@ public abstract class AbstractUpdateIntegrateCrawler implements SvnWCRootCrawler SVNUpdateClient client = myVcs.createUpdateClient(); client.setEventHandler(myHandler); - long rev = doUpdate(root, client); + long rev = doUpdate(root); if (rev < 0 && !isMerge()) { throw new SVNException(SVNErrorMessage.create(SVNErrorCode.UNKNOWN, SvnBundle.message("exception.text.root.was.not.properly.updated", root))); @@ -73,9 +73,7 @@ public abstract class AbstractUpdateIntegrateCrawler implements SvnWCRootCrawler protected abstract void showProgressMessage(ProgressIndicator progress, File root); - protected abstract long doUpdate( - File root, - SVNUpdateClient client) throws SVNException; + protected abstract long doUpdate(File root) throws SVNException; protected abstract boolean isMerge(); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/MergeRootInfo.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/MergeRootInfo.java index b8ea0520ebc2..96701b519111 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/MergeRootInfo.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/MergeRootInfo.java @@ -18,6 +18,7 @@ package org.jetbrains.idea.svn.update; import org.jetbrains.idea.svn.SvnVcs; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNInfo; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNWCClient; @@ -33,17 +34,9 @@ public class MergeRootInfo { myRevision1 = SVNRevision.HEAD; myRevision2 = SVNRevision.HEAD; - try { - SVNWCClient wcClient = vcs.createWCClient(); - final SVNURL url = wcClient.doInfo(file, SVNRevision.UNDEFINED).getURL(); - myUrl1 = url.toString(); - myUrl2 = url.toString(); - } - catch (SVNException e) { - myUrl1 = ""; - myUrl2 = ""; - } - + SVNInfo info = vcs.getInfo(file); + myUrl1 = info != null && info.getURL() != null ? info.getURL().toString() : ""; + myUrl2 = myUrl1; } public SVNURL getUrl1() { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnIntegrateEnvironment.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnIntegrateEnvironment.java index 9e8a5991f41c..9f4a95f7016f 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnIntegrateEnvironment.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnIntegrateEnvironment.java @@ -91,10 +91,7 @@ public class SvnIntegrateEnvironment extends AbstractSvnUpdateIntegrateEnvironme } } - protected long doUpdate( - final File root, - final SVNUpdateClient client) throws - SVNException { + protected long doUpdate(final File root) throws SVNException { final SvnConfiguration svnConfig = SvnConfiguration.getInstance(myVcs.getProject()); MergeRootInfo info = svnConfig.getMergeRootInfo(root, myVcs); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateEnvironment.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateEnvironment.java index 80e43aaf7828..5d2d4153be50 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateEnvironment.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateEnvironment.java @@ -33,7 +33,6 @@ import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.SVNInfo; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNUpdateClient; -import org.tmatesoft.svn.core.wc.SVNWCClient; import java.io.File; import java.util.ArrayList; @@ -76,10 +75,7 @@ public class SvnUpdateEnvironment extends AbstractSvnUpdateIntegrateEnvironment progress.setText(SvnBundle.message("progress.text.updating", root.getAbsolutePath())); } - protected long doUpdate( - final File root, - final SVNUpdateClient client) throws - SVNException { + protected long doUpdate(final File root) throws SVNException { final long rev; final SvnConfiguration configuration = SvnConfiguration.getInstance(myVcs.getProject()); @@ -104,13 +100,15 @@ public class SvnUpdateEnvironment extends AbstractSvnUpdateIntegrateEnvironment private SvnUpdateClientI createUpdateClient(SvnConfiguration configuration, File root, boolean isSwitch, SVNURL sourceUrl) { final SvnUpdateClientI updateClient; + boolean is18Format = myVcs.getWorkingCopyFormat(root) == WorkingCopyFormat.ONE_DOT_EIGHT; + // do not do from command line for switch now - if (! isSwitch && SvnConfiguration.UseAcceleration.commandLine.equals(configuration.myUseAcceleration) && + if (! isSwitch && (is18Format || SvnConfiguration.UseAcceleration.commandLine.equals(configuration.myUseAcceleration) && Svn17Detector.is17(myVcs.getProject(), root) && ( SvnAuthenticationManager.HTTP.equals(sourceUrl.getProtocol()) || SvnAuthenticationManager.HTTPS.equals(sourceUrl.getProtocol()) - )) { - updateClient = new SvnCommandLineUpdateClient(myVcs.getProject(), null); + ))) { + updateClient = new SvnCommandLineUpdateClient(myVcs, null); } else { updateClient = new SvnSvnkitUpdateClient(myVcs.createUpdateClient()); } @@ -129,18 +127,8 @@ public class SvnUpdateEnvironment extends AbstractSvnUpdateIntegrateEnvironment @Nullable private static SVNURL getSourceUrl(final SvnVcs vcs, final File root) { - try { - SVNWCClient wcClient = vcs.createWCClient(); - final SVNInfo svnInfo = wcClient.doInfo(root, SVNRevision.UNDEFINED); - if (svnInfo != null) { - return svnInfo.getURL(); - } else { - return null; - } - } - catch (SVNException e) { - return null; - } + final SVNInfo svnInfo = vcs.getInfo(root); + return svnInfo != null ? svnInfo.getURL() : null; } public boolean validateOptions(final Collection<FilePath> roots) { @@ -193,9 +181,8 @@ public class SvnUpdateEnvironment extends AbstractSvnUpdateIntegrateEnvironment // false - do not do update private boolean checkAncestry(final File sourceFile, final SVNURL targetUrl, final SVNRevision targetRevision) throws SVNException { - final SVNWCClient client = myVcs.createWCClient(); - final SVNInfo sourceSvnInfo = client.doInfo(sourceFile, SVNRevision.UNDEFINED); - final SVNInfo targetSvnInfo = client.doInfo(targetUrl, SVNRevision.UNDEFINED, targetRevision); + final SVNInfo sourceSvnInfo = myVcs.getInfo(sourceFile); + final SVNInfo targetSvnInfo = myVcs.getInfo(targetUrl, targetRevision); if (sourceSvnInfo == null || targetSvnInfo == null) { // cannot check diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/UpdateRootInfo.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/UpdateRootInfo.java index 4852e9f6b95f..201260e232f2 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/UpdateRootInfo.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/UpdateRootInfo.java @@ -31,20 +31,9 @@ public class UpdateRootInfo { public UpdateRootInfo(File file, SvnVcs vcs) { myRevision = SVNRevision.HEAD; - try { - SVNWCClient wcClient = vcs.createWCClient(); - SVNInfo info = wcClient.doInfo(file, SVNRevision.UNDEFINED); - if (info != null) { - final SVNURL url = info.getURL(); - myUrl = url.toString(); - } else { - myUrl = ""; - } - } - catch (SVNException e) { - myUrl = ""; - } + SVNInfo info = vcs.getInfo(file); + myUrl = info != null && info.getURL() != null ? info.getDepth().toString() : ""; } public SVNURL getUrl() { |