1 /* hooks.c : running repository hooks
3 * ====================================================================
4 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution. The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
12 * This software consists of voluntary contributions made by many
13 * individuals. For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
22 #include <apr_pools.h>
23 #include <apr_file_io.h>
26 #include <apr_portable.h>
31 #include "svn_error.h"
33 #include "svn_repos.h"
36 #include "svn_private_config.h"
40 /*** Hook drivers. ***/
43 /* Helper function for run_hook_cmd(). Wait for a hook to finish
44 executing and return either SVN_NO_ERROR if the hook script completed
45 without error, or an error describing the reason for failure.
47 NAME and CMD are the name and path of the hook program, CMD_PROC
48 is a pointer to the structure representing the running process,
49 and READ_ERRHANDLE is an open handle to the hook's stderr.
51 Hooks are considered to have failed if we are unable to wait for the
52 process, if we are unable to read from the hook's stderr, if the
53 process has failed to exit cleanly (due to a coredump, for example),
54 or if the process returned a non-zero return code.
56 Any error output returned by the hook's stderr will be included in an
57 error message, though the presence of output on stderr is not itself
58 a reason to fail a hook. */
60 check_hook_result(const char *name
, const char *cmd
, apr_proc_t
*cmd_proc
,
61 apr_file_t
*read_errhandle
, apr_pool_t
*pool
)
63 svn_error_t
*err
, *err2
;
64 svn_stringbuf_t
*native_stderr
, *failure_message
;
65 const char *utf8_stderr
;
67 apr_exit_why_e exitwhy
;
69 err2
= svn_stringbuf_from_aprfile(&native_stderr
, read_errhandle
, pool
);
71 err
= svn_io_wait_for_cmd(cmd_proc
, cmd
, &exitcode
, &exitwhy
, pool
);
74 svn_error_clear(err2
);
78 if (APR_PROC_CHECK_EXIT(exitwhy
) && exitcode
== 0)
80 /* The hook exited cleanly. However, if we got an error reading
81 the hook's stderr, fail the hook anyway, because this might be
82 symptomatic of a more important problem. */
85 return svn_error_createf
86 (SVN_ERR_REPOS_HOOK_FAILURE
, err2
,
87 _("'%s' hook succeeded, but error output could not be read"),
94 /* The hook script failed. */
96 /* If we got the stderr output okay, try to translate it into UTF-8.
97 Ensure there is something sensible in the UTF-8 string regardless. */
100 err2
= svn_utf_cstring_to_utf8(&utf8_stderr
, native_stderr
->data
, pool
);
102 utf8_stderr
= _("[Error output could not be translated from the "
103 "native locale to UTF-8.]");
107 utf8_stderr
= _("[Error output could not be read.]");
109 /*### It would be nice to include the text of any translation or read
110 error in the messages above before we clear it here. */
111 svn_error_clear(err2
);
113 if (!APR_PROC_CHECK_EXIT(exitwhy
))
115 failure_message
= svn_stringbuf_createf(pool
,
116 _("'%s' hook failed (did not exit cleanly: "
117 "apr_exit_why_e was %d, exitcode was %d). "),
118 name
, exitwhy
, exitcode
);
123 if (strcmp(name
, "start-commit") == 0
124 || strcmp(name
, "pre-commit") == 0)
125 action
= _("Commit");
126 else if (strcmp(name
, "pre-revprop-change") == 0)
127 action
= _("Revprop change");
128 else if (strcmp(name
, "pre-lock") == 0)
130 else if (strcmp(name
, "pre-unlock") == 0)
131 action
= _("Unlock");
135 failure_message
= svn_stringbuf_createf(
136 pool
, _("%s hook failed (exit code %d)"),
139 failure_message
= svn_stringbuf_createf(
140 pool
, _("%s blocked by %s hook (exit code %d)"),
141 action
, name
, exitcode
);
146 svn_stringbuf_appendcstr(failure_message
,
147 _(" with output:\n"));
148 svn_stringbuf_appendcstr(failure_message
, utf8_stderr
);
152 svn_stringbuf_appendcstr(failure_message
,
153 _(" with no output."));
156 return svn_error_create(SVN_ERR_REPOS_HOOK_FAILURE
, err
,
157 failure_message
->data
);
161 /* NAME, CMD and ARGS are the name, path to and arguments for the hook
162 program that is to be run. The hook's exit status will be checked,
163 and if an error occurred the hook's stderr output will be added to
166 If STDIN_HANDLE is non-null, pass it as the hook's stdin, else pass
167 no stdin to the hook. */
169 run_hook_cmd(const char *name
,
172 apr_file_t
*stdin_handle
,
176 apr_file_t
*read_errhandle
, *write_errhandle
, *null_handle
;
177 apr_status_t apr_err
;
181 /* Create a pipe to access stderr of the child. */
182 apr_err
= apr_file_pipe_create(&read_errhandle
, &write_errhandle
, pool
);
184 return svn_error_wrap_apr
185 (apr_err
, _("Can't create pipe for hook '%s'"), cmd
);
187 /* Pipes are inherited by default, but we don't want that, since
188 APR will duplicate the write end of the pipe for the child process.
189 Not closing the read end is harmless, but if the write end is inherited,
190 it will be inherited by grandchildren as well. This causes problems
191 if a hook script puts long-running jobs in the background. Even if
192 they redirect stderr to something else, the write end of our pipe will
193 still be open, causing us to block. */
194 apr_err
= apr_file_inherit_unset(read_errhandle
);
196 return svn_error_wrap_apr
197 (apr_err
, _("Can't make pipe read handle non-inherited for hook '%s'"),
200 apr_err
= apr_file_inherit_unset(write_errhandle
);
202 return svn_error_wrap_apr
203 (apr_err
, _("Can't make pipe write handle non-inherited for hook '%s'"),
207 /* Redirect stdout to the null device */
208 apr_err
= apr_file_open(&null_handle
, SVN_NULL_DEVICE_NAME
, APR_WRITE
,
209 APR_OS_DEFAULT
, pool
);
211 return svn_error_wrap_apr
212 (apr_err
, _("Can't create null stdout for hook '%s'"), cmd
);
214 err
= svn_io_start_cmd(&cmd_proc
, ".", cmd
, args
, FALSE
,
215 stdin_handle
, null_handle
, write_errhandle
, pool
);
217 /* This seems to be done automatically if we pass the third parameter of
218 apr_procattr_child_in/out_set(), but svn_io_run_cmd()'s interface does
219 not support those parameters. We need to close the write end of the
220 pipe so we don't hang on the read end later, if we need to read it. */
221 apr_err
= apr_file_close(write_errhandle
);
223 return svn_error_wrap_apr
224 (apr_err
, _("Error closing write end of stderr pipe"));
228 err
= svn_error_createf
229 (SVN_ERR_REPOS_HOOK_FAILURE
, err
, _("Failed to start '%s' hook"), cmd
);
233 err
= check_hook_result(name
, cmd
, &cmd_proc
, read_errhandle
, pool
);
236 /* Hooks are fallible, and so hook failure is "expected" to occur at
237 times. When such a failure happens we still want to close the pipe
239 apr_err
= apr_file_close(read_errhandle
);
241 return svn_error_wrap_apr
242 (apr_err
, _("Error closing read end of stderr pipe"));
244 apr_err
= apr_file_close(null_handle
);
246 return svn_error_wrap_apr(apr_err
, _("Error closing null file"));
250 #else /* Run hooks with spawn() on OS400. */
251 #define AS400_BUFFER_SIZE 256
253 const char **native_args
;
254 int fd_map
[3], stderr_pipe
[2], exitcode
;
255 svn_stringbuf_t
*script_output
;
256 pid_t child_pid
, wait_rv
;
257 apr_size_t args_arr_size
= 0, i
;
258 struct inheritance xmp_inherit
= {0};
260 /* Despite the UTF support in V5R4 a few functions still require
262 char *xmp_envp
[2] = {"QIBM_USE_DESCRIPTOR_STDIO=Y", NULL
};
263 const char *dev_null_ebcdic
= SVN_NULL_DEVICE_NAME
;
264 #pragma convert(1208)
266 /* Find number of elements in args array. */
267 while (args
[args_arr_size
] != NULL
)
270 /* Allocate memory for the native_args string array plus one for
271 * the ending null element. */
272 native_args
= apr_palloc(pool
, sizeof(char *) * args_arr_size
+ 1);
274 /* Convert UTF-8 args to EBCDIC for use by spawn(). */
275 for (i
= 0; args
[i
] != NULL
; i
++)
277 SVN_ERR(svn_utf_cstring_from_utf8_ex2((const char**)(&(native_args
[i
])),
278 args
[i
], (const char *)0,
282 /* Make the last element in the array a NULL pointer as required
284 native_args
[args_arr_size
] = NULL
;
289 /* Get OS400 file descriptor of APR stdin file and map it. */
290 if (apr_os_file_get(&fd_map
[0], stdin_handle
))
292 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
293 "Error converting APR file to OS400 "
294 "type for hook script '%s'", cmd
);
299 fd_map
[0] = open(dev_null_ebcdic
, O_RDONLY
);
302 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
303 "Error opening /dev/null for hook "
309 fd_map
[1] = open(dev_null_ebcdic
, O_WRONLY
);
311 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
312 "Error opening /dev/null for hook script '%s'",
316 /* Get pipe for hook's stderr. */
317 if (pipe(stderr_pipe
) != 0)
319 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
320 "Can't create stderr pipe for "
323 fd_map
[2] = stderr_pipe
[1];
325 /* Spawn the hook command. */
326 child_pid
= spawn(native_args
[0], 3, fd_map
, &xmp_inherit
, native_args
,
330 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
331 "Error spawning process for hook script '%s'",
335 /* Close the stdout file descriptor. */
336 if (close(fd_map
[1]) == -1)
337 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
338 "Error closing write end of stdout pipe to "
339 "hook script '%s'", cmd
);
341 /* Close the write end of the stderr pipe so any subsequent reads
343 if (close(fd_map
[2]) == -1)
344 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
345 "Error closing write end of stderr pipe to "
346 "hook script '%s'", cmd
);
348 script_output
= svn_stringbuf_create("", pool
);
354 svn_stringbuf_ensure(script_output
,
355 script_output
->len
+ AS400_BUFFER_SIZE
+ 1);
357 rc
= read(stderr_pipe
[0],
358 &(script_output
->data
[script_output
->len
]),
363 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
364 "Error reading stderr of hook "
368 script_output
->len
+= rc
;
370 /* If read() returned 0 then EOF was found and we are done reading
374 script_output
->data
[script_output
->len
] = '\0';
379 /* Close the read end of the stderr pipe. */
380 if (close(stderr_pipe
[0]) == -1)
381 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
382 "Error closing read end of stderr "
383 "pipe to hook script '%s'", cmd
);
385 /* Wait for the child process to complete. */
386 wait_rv
= waitpid(child_pid
, &exitcode
, 0);
389 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
390 "Error waiting for process completion of "
391 "hook script '%s'", cmd
);
395 if (WIFEXITED(exitcode
))
397 if (WEXITSTATUS(exitcode
))
400 const char *utf8_stderr
= NULL
;
401 svn_stringbuf_t
*failure_message
= svn_stringbuf_createf(
402 pool
, "'%s' hook failed (exited with a non-zero exitcode "
403 "of %d). ", name
, exitcode
);
405 if (!svn_stringbuf_isempty(script_output
))
407 /* OS400 scripts produce EBCDIC stderr, so convert it. */
408 err
= svn_utf_cstring_to_utf8_ex2(&utf8_stderr
,
410 (const char*)0, pool
);
413 utf8_stderr
= "[Error output could not be translated from "
414 "the native locale to UTF-8.]";
415 svn_error_clear(err
);
422 svn_stringbuf_appendcstr(failure_message
,
423 "The following error output was "
424 "produced by the hook:\n");
425 svn_stringbuf_appendcstr(failure_message
, utf8_stderr
);
429 svn_stringbuf_appendcstr(failure_message
,
430 "No error output was produced by "
433 return svn_error_create(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
434 failure_message
->data
);
440 else if (WIFSIGNALED(exitcode
))
442 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
443 "Process '%s' failed because of an "
444 "uncaught terminating signal", cmd
);
446 else if (WIFEXCEPTION(exitcode
))
448 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
449 "Process '%s' failed unexpectedly with "
450 "OS400 exception %d", cmd
,
451 WEXCEPTNUMBER(exitcode
));
453 else if (WIFSTOPPED(exitcode
))
455 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
456 "Process '%s' stopped unexpectedly by "
457 "signal %d", cmd
, WSTOPSIG(exitcode
));
461 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
462 "Process '%s' failed unexpectedly", cmd
);
468 /* Create a temporary file F that will automatically be deleted when it is
469 closed. Fill it with VALUE, and leave it open and rewound, ready to be
472 create_temp_file(apr_file_t
**f
, const svn_string_t
*value
, apr_pool_t
*pool
)
475 apr_off_t offset
= 0;
477 SVN_ERR(svn_io_temp_dir(&dir
, pool
));
478 SVN_ERR(svn_io_open_unique_file2(f
, NULL
,
479 svn_path_join(dir
, "hook-input", pool
),
480 "", svn_io_file_del_on_close
, pool
));
481 SVN_ERR(svn_io_file_write_full(*f
, value
->data
, value
->len
, NULL
, pool
));
482 SVN_ERR(svn_io_file_seek(*f
, APR_SET
, &offset
, pool
));
487 /* Check if the HOOK program exists and is a file or a symbolic link, using
488 POOL for temporary allocations.
490 If the hook exists but is a broken symbolic link, set *BROKEN_LINK
491 to TRUE, else if the hook program exists set *BROKEN_LINK to FALSE.
493 Return the hook program if found, else return NULL and don't touch
497 check_hook_cmd(const char *hook
, svn_boolean_t
*broken_link
, apr_pool_t
*pool
)
499 static const char* const check_extns
[] = {
501 /* For WIN32, we need to check with file name extension(s) added.
503 As Windows Scripting Host (.wsf) files can accomodate (at least)
504 JavaScript (.js) and VB Script (.vbs) code, extensions for the
505 corresponding file types need not be enumerated explicitly. */
506 ".exe", ".cmd", ".bat", ".wsf", /* ### Any other extensions? */
513 const char *const *extn
;
514 svn_error_t
*err
= NULL
;
515 svn_boolean_t is_special
;
516 for (extn
= check_extns
; *extn
; ++extn
)
518 const char *const hook_path
=
519 (**extn
? apr_pstrcat(pool
, hook
, *extn
, 0) : hook
);
521 svn_node_kind_t kind
;
522 if (!(err
= svn_io_check_resolved_path(hook_path
, &kind
, pool
))
523 && kind
== svn_node_file
)
525 *broken_link
= FALSE
;
528 svn_error_clear(err
);
529 if (!(err
= svn_io_check_special_path(hook_path
, &kind
, &is_special
,
531 && is_special
== TRUE
)
536 svn_error_clear(err
);
542 /* Return an error for the failure of HOOK due to a broken symlink. */
544 hook_symlink_error(const char *hook
)
546 return svn_error_createf
547 (SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
548 _("Failed to run '%s' hook; broken symlink"), hook
);
552 svn_repos__hooks_start_commit(svn_repos_t
*repos
,
554 apr_array_header_t
*capabilities
,
557 const char *hook
= svn_repos_start_commit_hook(repos
, pool
);
558 svn_boolean_t broken_link
;
560 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
562 return hook_symlink_error(hook
);
567 char *capabilities_string
= svn_cstring_join(capabilities
, ":", pool
);
569 /* Get rid of that annoying final colon. */
570 if (capabilities_string
[0])
571 capabilities_string
[strlen(capabilities_string
) - 1] = '\0';
574 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
575 args
[2] = user
? user
: "";
576 args
[3] = capabilities_string
;
579 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_START_COMMIT
, hook
, args
, NULL
,
588 svn_repos__hooks_pre_commit(svn_repos_t
*repos
,
589 const char *txn_name
,
592 const char *hook
= svn_repos_pre_commit_hook(repos
, pool
);
593 svn_boolean_t broken_link
;
595 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
597 return hook_symlink_error(hook
);
604 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
608 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_PRE_COMMIT
, hook
, args
, NULL
,
617 svn_repos__hooks_post_commit(svn_repos_t
*repos
,
621 const char *hook
= svn_repos_post_commit_hook(repos
, pool
);
622 svn_boolean_t broken_link
;
624 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
626 return hook_symlink_error(hook
);
633 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
634 args
[2] = apr_psprintf(pool
, "%ld", rev
);
637 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_POST_COMMIT
, hook
, args
, NULL
,
646 svn_repos__hooks_pre_revprop_change(svn_repos_t
*repos
,
650 const svn_string_t
*new_value
,
654 const char *hook
= svn_repos_pre_revprop_change_hook(repos
, pool
);
655 svn_boolean_t broken_link
;
657 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
659 return hook_symlink_error(hook
);
664 apr_file_t
*stdin_handle
= NULL
;
665 char action_string
[2];
667 /* Pass the new value as stdin to hook */
669 SVN_ERR(create_temp_file(&stdin_handle
, new_value
, pool
));
671 SVN_ERR(svn_io_file_open(&stdin_handle
, SVN_NULL_DEVICE_NAME
,
672 APR_READ
, APR_OS_DEFAULT
, pool
));
674 action_string
[0] = action
;
675 action_string
[1] = '\0';
678 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
679 args
[2] = apr_psprintf(pool
, "%ld", rev
);
680 args
[3] = author
? author
: "";
682 args
[5] = action_string
;
685 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_PRE_REVPROP_CHANGE
, hook
, args
,
686 stdin_handle
, pool
));
688 SVN_ERR(svn_io_file_close(stdin_handle
, pool
));
692 /* If the pre- hook doesn't exist at all, then default to
693 MASSIVE PARANOIA. Changing revision properties is a lossy
694 operation; so unless the repository admininstrator has
695 *deliberately* created the pre-hook, disallow all changes. */
698 (SVN_ERR_REPOS_DISABLED_FEATURE
, NULL
,
699 _("Repository has not been enabled to accept revision propchanges;\n"
700 "ask the administrator to create a pre-revprop-change hook"));
708 svn_repos__hooks_post_revprop_change(svn_repos_t
*repos
,
712 svn_string_t
*old_value
,
716 const char *hook
= svn_repos_post_revprop_change_hook(repos
, pool
);
717 svn_boolean_t broken_link
;
719 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
721 return hook_symlink_error(hook
);
726 apr_file_t
*stdin_handle
= NULL
;
727 char action_string
[2];
729 /* Pass the old value as stdin to hook */
731 SVN_ERR(create_temp_file(&stdin_handle
, old_value
, pool
));
733 SVN_ERR(svn_io_file_open(&stdin_handle
, SVN_NULL_DEVICE_NAME
,
734 APR_READ
, APR_OS_DEFAULT
, pool
));
736 action_string
[0] = action
;
737 action_string
[1] = '\0';
740 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
741 args
[2] = apr_psprintf(pool
, "%ld", rev
);
742 args
[3] = author
? author
: "";
744 args
[5] = action_string
;
747 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_POST_REVPROP_CHANGE
, hook
, args
,
748 stdin_handle
, pool
));
750 SVN_ERR(svn_io_file_close(stdin_handle
, pool
));
759 svn_repos__hooks_pre_lock(svn_repos_t
*repos
,
761 const char *username
,
764 const char *hook
= svn_repos_pre_lock_hook(repos
, pool
);
765 svn_boolean_t broken_link
;
767 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
769 return hook_symlink_error(hook
);
776 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
781 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_PRE_LOCK
, hook
, args
, NULL
, pool
));
789 svn_repos__hooks_post_lock(svn_repos_t
*repos
,
790 apr_array_header_t
*paths
,
791 const char *username
,
794 const char *hook
= svn_repos_post_lock_hook(repos
, pool
);
795 svn_boolean_t broken_link
;
797 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
799 return hook_symlink_error(hook
);
804 apr_file_t
*stdin_handle
= NULL
;
805 svn_string_t
*paths_str
= svn_string_create(svn_cstring_join
809 SVN_ERR(create_temp_file(&stdin_handle
, paths_str
, pool
));
812 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
817 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_POST_LOCK
, hook
, args
, stdin_handle
,
820 SVN_ERR(svn_io_file_close(stdin_handle
, pool
));
828 svn_repos__hooks_pre_unlock(svn_repos_t
*repos
,
830 const char *username
,
833 const char *hook
= svn_repos_pre_unlock_hook(repos
, pool
);
834 svn_boolean_t broken_link
;
836 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
838 return hook_symlink_error(hook
);
845 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
847 args
[3] = username
? username
: "";
850 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_PRE_UNLOCK
, hook
, args
, NULL
,
859 svn_repos__hooks_post_unlock(svn_repos_t
*repos
,
860 apr_array_header_t
*paths
,
861 const char *username
,
864 const char *hook
= svn_repos_post_unlock_hook(repos
, pool
);
865 svn_boolean_t broken_link
;
867 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
869 return hook_symlink_error(hook
);
874 apr_file_t
*stdin_handle
= NULL
;
875 svn_string_t
*paths_str
= svn_string_create(svn_cstring_join
879 SVN_ERR(create_temp_file(&stdin_handle
, paths_str
, pool
));
882 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
883 args
[2] = username
? username
: "";
887 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_POST_UNLOCK
, hook
, args
,
888 stdin_handle
, pool
));
890 SVN_ERR(svn_io_file_close(stdin_handle
, pool
));
899 * vim:ts=4:sw=4:expandtab:tw=80:fo=tcroq
900 * vim:isk=a-z,A-Z,48-57,_,.,-,>
901 * vim:cino=>1s,e0,n0,f0,{.5s,}0,^-.5s,=.5s,t0,+1s,c3,(0,u0,\:0