Merge branch 'mh/ref-remove-empty-directory'

Deletion of a branch "foo/bar" could remove .git/refs/heads/foo
once there no longer is any other branch whose name begins with
"foo/", but we didn't do so so far.  Now we do.

* mh/ref-remove-empty-directory: (23 commits)
  files_transaction_commit(): clean up empty directories
  try_remove_empty_parents(): teach to remove parents of reflogs, too
  try_remove_empty_parents(): don't trash argument contents
  try_remove_empty_parents(): rename parameter "name" -> "refname"
  delete_ref_loose(): inline function
  delete_ref_loose(): derive loose reference path from lock
  log_ref_write_1(): inline function
  log_ref_setup(): manage the name of the reflog file internally
  log_ref_write_1(): don't depend on logfile argument
  log_ref_setup(): pass the open file descriptor back to the caller
  log_ref_setup(): improve robustness against races
  log_ref_setup(): separate code for create vs non-create
  log_ref_write(): inline function
  rename_tmp_log(): improve error reporting
  rename_tmp_log(): use raceproof_create_file()
  lock_ref_sha1_basic(): use raceproof_create_file()
  lock_ref_sha1_basic(): inline constant
  raceproof_create_file(): new function
  safe_create_leading_directories(): set errno on SCLD_EXISTS
  safe_create_leading_directories_const(): preserve errno
  ...
This commit is contained in:
Junio C Hamano
2017-02-27 13:57:12 -08:00
6 changed files with 359 additions and 203 deletions

View File

@ -129,8 +129,10 @@ enum scld_error safe_create_leading_directories(char *path)
*slash = '\0';
if (!stat(path, &st)) {
/* path exists */
if (!S_ISDIR(st.st_mode))
if (!S_ISDIR(st.st_mode)) {
errno = ENOTDIR;
ret = SCLD_EXISTS;
}
} else if (mkdir(path, 0777)) {
if (errno == EEXIST &&
!stat(path, &st) && S_ISDIR(st.st_mode))
@ -158,13 +160,85 @@ enum scld_error safe_create_leading_directories(char *path)
enum scld_error safe_create_leading_directories_const(const char *path)
{
int save_errno;
/* path points to cache entries, so xstrdup before messing with it */
char *buf = xstrdup(path);
enum scld_error result = safe_create_leading_directories(buf);
save_errno = errno;
free(buf);
errno = save_errno;
return result;
}
int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
{
/*
* The number of times we will try to remove empty directories
* in the way of path. This is only 1 because if another
* process is racily creating directories that conflict with
* us, we don't want to fight against them.
*/
int remove_directories_remaining = 1;
/*
* The number of times that we will try to create the
* directories containing path. We are willing to attempt this
* more than once, because another process could be trying to
* clean up empty directories at the same time as we are
* trying to create them.
*/
int create_directories_remaining = 3;
/* A scratch copy of path, filled lazily if we need it: */
struct strbuf path_copy = STRBUF_INIT;
int ret, save_errno;
/* Sanity check: */
assert(*path);
retry_fn:
ret = fn(path, cb);
save_errno = errno;
if (!ret)
goto out;
if (errno == EISDIR && remove_directories_remaining-- > 0) {
/*
* A directory is in the way. Maybe it is empty; try
* to remove it:
*/
if (!path_copy.len)
strbuf_addstr(&path_copy, path);
if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY))
goto retry_fn;
} else if (errno == ENOENT && create_directories_remaining-- > 0) {
/*
* Maybe the containing directory didn't exist, or
* maybe it was just deleted by a process that is
* racing with us to clean up empty directories. Try
* to create it:
*/
enum scld_error scld_result;
if (!path_copy.len)
strbuf_addstr(&path_copy, path);
do {
scld_result = safe_create_leading_directories(path_copy.buf);
if (scld_result == SCLD_OK)
goto retry_fn;
} while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0);
}
out:
strbuf_release(&path_copy);
errno = save_errno;
return ret;
}
static void fill_sha1_path(struct strbuf *buf, const unsigned char *sha1)
{
int i;