Submodules management in Git

Git’s submodule tool allows you to create a child repository as a submodule inside a repository. This article shows you how to manipulate a submodule.

Add a submodule from a remote repository

To create a new sub repository whose content does not exist in the repository:

# Add a submodule named a-submodule from an remote repository
# This command will create a .gitmodules which contains the mapping relation of
# the submodule and its remote repository.
# It also stages the changes.
$ git submodule add

# Use status to check what the above command stages
$ git status
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   .gitmodules
        new file:   a-submodule

# You need to commit staged changes: .gitmodules and a-submodule
$ git commit -m 'add a submodule'
[master 9d7d3b3] add: add a submodule
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 a-submodule

As you see, Git treats the new submodule as a file with special mode (1600000) in a commit.

For a repository with submodules inside it, it is called the main repository.

Note: In new git versions, the submodule’ s .git data is stored the main repository’s .git directory while they stay in the submodule’s directory in the old versions.

Add a submodule from an subdirectory

You can also switch a subdirectory to a submodule. To add a submodule from an subdirectory which has been tracked, you need to remove it first:

# First remove the subdirectory named csvlib from index
$ git rm --cached csvlib

Then add a subdirectory as a submodule:

# Init csvlib as a repo
$ cd csvlib
$ git init
$ git add .
$ git commit -m 'initial commit'

# Add csvlib repo as a submodule
$ git submodule add ./csvlib/
Adding existing repo at 'csvlib' to the index

# Check status
$ git status
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   .gitmodules
        new file:   csvlib
        deleted:    csvlib/

# Commit the changes
$ git commit -m 'switch subdirectory csvlib as a submodule'
[master 9d7d3b3] add: add submodule git-submodule
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 a-submodule

Note: If you switch to a branch which the subdirectory not being as a submodule,you may get an error for the subdirectory will be overwritten. At this time, an option -f is needed to checkout. Afterwards if you get back to the branch treating it as a submodule, you need to execute git checkout . in the submodule’s directory to get the content back. See more on git submodules.

Commit changes for a submodule

To commit changes in a submodule, first commit in its own repository and then commit for the main repository:

# Commit changes in the submodule directory
$ git add 
$ git commit -m 'feat: add some functionly'
# Push changes to its remote repository
$ git push

# Switch to the main repository directory
$ cd ..
# Check changes from a submodule
$ git status
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
  (commit or discard the untracked or modified content in submodules)

        modified:   a-submodule (modified content)

# Commit the submodule's changes to the top repository
$ git add a-submodule
$ git commit -m 'update changes of submodule a-submodule'
# Push changes if you want
$ git push

Pull updates for a submodule

To pull updates from remote for a submodule, an easy way is to run:

# Let Git to go into the submodule's directory and get updates for a-submodule
# --merge, merge local work
$ git submodule update --remote --merge a-submodule

Or you can do it manually:

# Switch to the submodule's directory
$ cd a-submodule

# Pull and merge updates form upstream branch
$ git pull
$ git merge origin/master

Manipulate multiple submodules at a time

Git has a submodule foreach command for you to manipulate multiple submodules. For example, stashing changes for all your submodules:

# Stash changes for all submodules
$ git submodule foreach 'git stash'

Remove a submodule from a repository

To remove a submodule named a-submodule from a repository:

# Frist remove the from index
$ git rm --cached a-submodule

# If you want to keep submodule's files in working tree.
# Rename it for the next command will delete it from working tree
$ mv a-submodule a-submodule-renamed

# Unregister the submodule
# This command removes it both from .git/config and working tree
$ git submodule deinit a-submodule

Adding categories navigation to WordPress header

To show categories in the header part of your website, create a new division in the header where you want them to display.

Before you start, you’d better create a child theme to make customization instead of directly modifying a theme. Take the default “twentyseventeen” theme for example, add below code to file templates-parts/header/site-branding.php of the theme:

       <div id="category-list">
                <li><a title="Home Page" href="index.php">home</a></li>
                <?php wp_list_categories(array('title_li' => '', 'echo' => 1)); ?>
        </div><!-- .category-list -->

wp_list_categories is used to display the categories list. You can use many parameters to control its result, below are some of them:

  • title_li, text for the list title <li> element

  • echo, display markup marks.

  • separator, separator between links.
  • exclude, array of strings to exclude.

Add below code in style.css to style the categories:

/* category list in header */
#category-list {
    margin: 0;
    display: inline-block;
    float: right;
    vertical-align: middle;
    font-size: 14px;
    font-style: normal;
    text-transform: capitalize;
#category-list li {
    display: inline; 
    list-style-type: none; 
    padding: 0px 5px;
#category-list a:link, #category-list a:visited {
    color: #eee;
#category-list a:hover {
    opacity: 0.7;

The display and list-style-type make the list a horizontal layout.

Here is the header with categories:

categories in header

Listing changed files in a commit

Use below command to list the changed files in a commit:

# List changed files in the specified commit
$ git show --name-only <commit>

# Examples

# List changed files in HEAD
$ git show --name-only HEAD
commit ea34837d870e48106ae9ad09f41297a64ad6a6a1 (HEAD -> master)
Author: xxx <>
Date:   Wed Mar 13 20:27:46 2019 +0800

    feat: add localization


If you only want the file names, use:

# List only names of changed files in HEAD
$ git diff --name-only HEAD~ HEAD

Exporting data to csv in WordPress with PHP

In WordPress plugin development, sometimes you may need to export data to a csv file. Here we share how to implement it with PHP code.

How to export a csv file with PHP code

header() is used to send a raw HTTP header. To export a generated csv file, use below headers to tell the browser display a save dialog.

header( 'Content-Type: text/csv' ); // Supply the mime type
header( 'Content-Disposition: attachment; filename="downloaded.pdf"' ); // Supply a file name to save


There is not official RFC document for CSV files. text/csv is not a standard mime type, but it is more clear and works fine. application/octet-stream can also be used for csv files. However it is a very generous and it does not hint which application should be used to open the file.

Use two more headers to tell the content should not be cached by browser or any caches between the server and the browser.

header( "Cache-Control: no-cache, must-revalidate" );
header( "Expires: Sat, 26 Jul 1997 05:00:00 GMT" ); // Date in the past

Then echo the generated csv content directly:

echo $csv;


Any actual content must be output after header(). One common error is outputting content before header() by include or require a file. See more about header().

Below is the full example.

Example: Export data to csv in WordPress

Add an export button:

<a href="admin.php?page=export_example&export=table&noheader=1">Export</a>

Don’t forget add the argument named “noheader”. Otherwise, the csv content will directly be printed and there will be no Save dialog box popping up in the browser.

Process the export in the same file:

$table_head = array( 'column1', 'column2', 'column3' );
$table_body = array(
    array( 'a', 'b', 'c' ),
    array( 'd', 'e', 'f' )

// Process export
if( isset( $_GET['export'] ) ) {
    $csv = implode( $table_head, ',' );
    $csv .= "n"; // important! Make sure to use use double quotation marks.
    foreach( table_body as row ) {
        $csv .= implode( $row, ',' );
        $csv .= "n";

    $filename = 'table.csv';    
    header( 'Content-Type: text/csv' ); // tells browser to download
    header( 'Content-Disposition: attachment; filename="' . $filename .'"' );
    header( 'Pragma: no-cache' ); // no cache
    header( "Expires: Sat, 26 Jul 1997 05:00:00 GMT" ); // expire date

    echo $csv;
<a href="admin.php?page=export_example&export=table&noheader=1">Export</a>

This example works well on Firefox, IE, Chrome.

Export data with Unicode characters to utf-8 bom csv

If your data contains Unicode characters, you may want to export a utf-8 bom csv that Excel will read properly. To achieve that, just output the BOM mark before data:

echo "xEFxBBxBF"; // UTF-8 BOM
echo $csv;

Fixing JetPack related posts not showing for XMLRPC parse error

JetPack related posts are not showing, WordPress says This site cannot be accessed, and xmlrpc test shows parse error. not well formed . Here it shows you how to fix it.

Test your site

Run below command to test your site in an terminal (Any computer is fine.)

$ curl -A "Jetpack by" -d "<methodCall><methodName>demo.sayHello</methodName></methodCall>" https://www.your

The correct result should be:

<?xml version="1.0" encoding="UTF-8"?>

It is a XMLRPC parse error if the response is:

<?xml version="1.0" encoding="UTF-8"?>
          <value><string>parse error. not well formed</string></value>

Tips: You can also test your site using Just input your site and click Check button. Other information like username and password is not needed.

If it is an XMLRPC parse error, the result will be:

Code      Description
-32700    parse error. not well formed

Solution for parse error

Connect to your website host, run below command to install php-xml:

$ sudo apt-get install php-xml

If your host does not have apt-get installed, you may need use (Such as AWS EC2 with Amazon Linux 2):

$ sudo yum install php-xml

Retest your site, it should be OK (Restart your apache sever if needed). After your posts are synced to WordPress site, you may see related posts.

Note: Related posts will not appear unless at least 3 good related posts can be found by JetPack. See more on jetpack related posts.

Tips: For other JectPack connection issues,fix them according to fixing jetpack connection issues.