Deep Analysis and Solution for Git Push Error: Unable to Unlink Old (Permission Denied)

Nov 21, 2025 · Programming · 14 views · 7.8

Keywords: Git permissions | directory write permissions | unlink operation

Abstract: This paper provides an in-depth analysis of the 'Unable to unlink old (Permission denied)' error during Git push operations, revealing that the root cause lies in directory write permissions rather than file permissions. Through detailed permission mechanism analysis, code examples, and practical scenario validation, it offers comprehensive solutions and best practices to help developers completely resolve such permission issues.

Problem Phenomenon and Background

In remote server environments, when post-receive hooks are configured to automatically perform Git checkout operations, developers frequently encounter a confusing permission error. The specific manifestation is:

#!/bin/sh
GIT_WORK_TREE=/var/www/<website> git checkout -f

When pushing from a local machine to the server Git repository, the system repeatedly reports:

remote: error: unable to unlink old '<file>' (Permission denied)

This error occurs repeatedly for almost all files, even when these files have exactly the same ownership and permissions as README.txt files that operate normally:

-rw-r--r--  1 <serverusername>  <serverusername>  2939 Aug  2 10:58 README.txt

Root Cause Analysis

The core of the problem lies in misunderstanding the Unix/Linux file system permission mechanism. When Git needs to update or delete files, it actually performs operations on the directory containing the file, not directly on the file itself.

In Unix file systems, the unlink operation essentially removes the file reference from directory entries. This process requires the following permissions:

Let's understand this mechanism through a concrete code example. Suppose we have a directory structure:

# Check directory permissions
ls -ld /var/www/website
drwxr-xr-x 5 serverusername serverusername 4096 Aug 10 10:00 /var/www/website

# Check file permissions  
ls -l /var/www/website/index.html
-rw-r--r-- 1 serverusername serverusername 1024 Aug 10 10:00 index.html

In this scenario, although the files have appropriate read-write permissions, the directory lacks write permission (group and others have no write permission in drwxr-xr-x), which causes the unlink operation to fail.

Solution Implementation

Based on understanding the root cause of the problem, we can provide several effective solutions:

Solution 1: Fix Directory Permissions

The most direct solution is to ensure the Git work tree directory has appropriate write permissions:

# Add write permissions to the directory
sudo chmod -R ug+w /var/www/website

# Verify permission settings
ls -ld /var/www/website
drwxrwxr-x 5 serverusername serverusername 4096 Aug 10 10:00 /var/www/website

This command recursively adds write permissions for owner and group, ensuring Git can perform unlink operations in the directory.

Solution 2: Best Practices for Ownership and Permissions

To build more robust deployment workflows, we recommend adopting the following best practices:

# Create dedicated deployment user and group
sudo groupadd deployers
sudo useradd -g deployers deployuser

# Set directory ownership
sudo chown -R deployuser:deployers /var/www/website

# Set appropriate permissions
sudo chmod -R 775 /var/www/website
sudo find /var/www/website -type f -exec chmod 664 {} \;

Practical Scenario Validation

Referring to similar issues in GitHub Actions, we can see the prevalence of permission problems in automated deployment workflows. In CI/CD environments, ensuring the running user has appropriate permissions for the working directory is crucial:

# Permission fix example in GitHub Actions
- name: Fix permissions
  run: |
    sudo chown -R $USER:$USER .
    sudo chmod -R ug+w .
    find . -type f -exec chmod 644 {} \;
    find . -type d -exec chmod 755 {} \;

In-depth Technical Principles

To fully understand this issue, we need to delve into the Unix file system's inode and directory entry mechanisms. Each file in the file system has an inode containing file metadata, while a directory is actually a special file containing a mapping list from filenames to inodes.

When performing an unlink operation:

# Pseudo-code simulating unlink operation
function unlink_file(directory_path, filename) {
    # 1. Open directory file (requires directory execute permission)
    dir_fd = open(directory_path, O_RDONLY | O_DIRECTORY)
    
    # 2. Remove filename to inode mapping from directory entries (requires directory write permission)
    unlinkat(dir_fd, filename, 0)
    
    # 3. If this is the last hard link to the file, release inode and disk space
    if (inode.link_count == 0) {
        free_inode(inode)
    }
}

This process clearly demonstrates why directory permissions are so critical—the core of the unlink operation is modifying the directory file.

Preventive Measures and Best Practices

To avoid similar problems, we recommend implementing the following measures in project deployment workflows:

By deeply understanding file system permission mechanisms and Git operation principles, developers can effectively diagnose and resolve such permission issues, ensuring the stability and reliability of deployment workflows.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.