Friday, August 28, 2026

Merging Two Git Repositories with Combined Histories

This procedure merges two Git repositories while preserving the complete commit history of both repositories.

Goal

We have:

  • Repo A — the final/main repository
  • Repo B — the repository being imported
  • Repo B should appear inside Repo A under the priorwork/ directory
  • Both repositories use the main branch

The final layout should look like:

repo-A/
├── existing files from Repo A
└── priorwork/
    ├── files from Repo B
    └── ...

Both repositories' Git histories will be preserved.


1. Start with a fresh clone of Repo B

Do not run git filter-repo inside Repo A.

Clone Repo B into a separate directory:

git clone <REPO-B-URL> repo-B
cd repo-B

Make sure you are on Repo B's main branch:

git checkout main

2. Rewrite Repo B so its files live under priorwork/

Run this command inside the fresh Repo B clone:

git filter-repo --to-subdirectory-filter priorwork

Because this is a fresh clone, you should not need --force.

Verify the result:

git ls-tree -r HEAD --name-only | head -20

You should see paths like:

priorwork/.gitignore
priorwork/README.md
priorwork/file1...
priorwork/src/...

This confirms that Repo B's files have been moved under priorwork/ in its history.


3. Go to Repo A

Switch to the final repository:

cd ../repo-A

Make sure you are on the branch that should receive Repo B:

git checkout main

Check that your working tree is clean:

git status

Ideally, Git should report:

nothing to commit, working tree clean

4. Add the rewritten Repo B as a remote

From inside Repo A:

git remote add priorwork ../repo-B

Verify:

git remote -v

You should see something similar to:

origin     <REPO-A-URL>
priorwork  ../repo-B

Here, priorwork is simply the name of the Git remote. It is not a directory in the working tree.


5. Fetch Repo B's rewritten history

Run:

git fetch priorwork

This downloads Repo B's Git history into Repo A's .git directory.

It does not create a priorwork/ directory in the working tree yet.

Verify that Repo B's files are stored under the desired path:

git ls-tree -r priorwork/main --name-only | head -20

You should see:

priorwork/.gitignore
priorwork/README.md
priorwork/file1...
priorwork/src/...

This confirms that Repo B was correctly rewritten before the merge.


6. Merge the two histories

Now merge Repo B into Repo A:

git merge priorwork/main --allow-unrelated-histories

The --allow-unrelated-histories option is required because Repo A and Repo B originally had independent Git histories.

If there are no conflicts, the merge should complete successfully.

The working tree should now look approximately like:

repo-A/
├── .git/
├── <Repo A files>
├── ...
└── priorwork/
    ├── .gitignore
    ├── README.md
    ├── file1...
    └── src/
        ├── ...
        └── ...

7. Verify that both histories were preserved

View the complete commit graph:

git log --graph --oneline --all --decorate

You should see the two independent histories joining at the merge commit.

You can also inspect Repo B's history:

git log --oneline priorwork/main

And inspect all history:

git log --oneline --all

8. Push the combined repository

Once everything has been verified:

git push origin main

The remote Repo A repository will now contain:

  • Repo A's original files
  • Repo B's files under priorwork/
  • Repo A's complete Git history
  • Repo B's complete Git history
  • A merge commit connecting the two histories

Important Concept

The overall process is:

             Rewrite Repo B
Repo B ───────────────────────────►
                                    Repo B'
                                    priorwork/
                                        │
                                        │
Repo A ───────────────────────────────►│
                 merge                │
                                        ▼
                             Combined Repo A
                             ├── Repo A files
                             └── priorwork/
                                 └── Repo B files

The most important rule is:

Run git filter-repo on a fresh clone of Repo B, not on Repo A.


Quick Command Summary

Repo B

git clone <REPO-B-URL> repo-B
cd repo-B
git checkout main
git filter-repo --to-subdirectory-filter priorwork
git ls-tree -r HEAD --name-only | head -20

Repo A

cd ../repo-A
git checkout main
git status

git remote add priorwork ../repo-B
git fetch priorwork

git ls-tree -r priorwork/main --name-only | head -20

git merge priorwork/main --allow-unrelated-histories

Verify and push

git log --graph --oneline --all --decorate
git push origin main

Expected Result

repo-A/
├── files originally belonging to Repo A
└── priorwork/
    └── files originally belonging to Repo B

Both repositories' commit histories are preserved.

Sunday, December 14, 2025

Dealing with Windows Update Errors

This is just a bookmark for the following Microsoft resource:
Windows update errors aren't uncommon. I had some difficult to resolve Windows update errors, and the resource is useful:
  1. It lists the error codes and their meaning,
  2. It provides common steps to resolve the errors.
  3. When all failed, it provides manual steps, such as examining CBS log.

Wednesday, September 24, 2025

Updating TexLive's CTAN Repository

I encountered an error when attempting to install a LaTeX package using tlgmr

tlmgr intall Package_Name

A solution to address is to change the default repository or declare one on the command line

Declaring the repository on the command line can be cubsome. We can reset the default CTAN repository using command

tlmgr option repository https://mirror.ctan.org/systems/texlive/tlnet

The above command will select one and set it as the default one. However, if you wish to specify which one to use, we can use command

tlmgr option repository https://mirrors.ibiblio.org/pub/mirrors/CTAN/systems/texlive/tlnet

The mirrors can be looked up from the CTAN mirror page.

Monday, April 21, 2025

Monitoring transient network traffic session

 Sometimes there is a need to investigate network traffic that is transient. To make the problem clearer, let's examine this example. The firewall indicates some network traffic was blocked:


Block IPv4 link-local (1000000102) 192.168.99.99:35018 169.254.169.254:80 TCP:S 

We  want to figure out which process that sent out the packets. So, we would do something like


sudo netstat -anp | grep 35018

Unfortunately, this yields nothing because at the time we issue the netstat command port 35018 is not open. It turns out the network traffic is short-lived. How do we figure out which process sends out the packets? Of course, we could try to capture the packets:


tcpdump -XX -i any host 169.254.169.254 and port 80

which indeed captures the packets, and also shows the header and content of the packets captured. Sometimes, the packet header and the content are sufficiently for us to figure out what progress sent out the packets. However, what if the packet header and the content do not offer a clue?

It turns out, we can use sysdig, for instance, we can use it in this way:


sysdig -p '*%evt.num  %evt.time   %evt.cpu   %proc.name   (%thread.tid %proc.ppid)   %evt.dir %evt.type %evt.info' fd.rip=169.254.169.254 and fd.rport=80

which tells us the process that sent out the packets and the parent process PID. The process that sent out the packets may have gone, but it is offen that the parent process is still around. This solves us the problem because it offers a way to investigate further.

Tuesday, March 11, 2025

Installing TexLive Packages Using tlmgr from a Non-default Repository

From time to time, the default TexLive repository does not work for me when I try to install a package using tlmgr. One method to get around this is to use a non-default repository, e.g., to install the listings package, we can


tlmgr -repository https://mirrors.ibiblio.org/pub/mirrors/CTAN/systems/texlive/tlnet install listings

Perhaps, the trick part is not to find a mirror, rather it is to write the correct URL. This example serves as a template for that

Monday, March 3, 2025

Selecting CUDA Devices

I observed that when I run a Pytorch program on a system with GPUs, the Pytorch runner dispatches the computational tasks to both GPUs. Since the program is not optimized for using multiple GPUs, the performance using the two GPU is worse than just using one. A simple method to address this turns out to be that we inform Pytorch to use a designated GPU via environmental variable CUDA_VISIBLE_DEVICES.

For instance, to run a task run_task.sh, we can 

 CUDA_VISIBLE_DEVICES=0 ./run_task.sh SEED=1234

which results in running the task on a single GPU. 

For the non-optimized program, I got much better computational efficiency by doing than letting each run on two GPUs:

 CUDA_VISIBLE_DEVICES=0 ./run_task.sh SEED=1234

 CUDA_VISIBLE_DEVICES=1 ./run_task.sh SEED=4321




 


 

Friday, February 21, 2025

Enabling NAT and IP Masquerading on Rocky Linux 9

This is a note about enabling NAT (SNAT, more precisely) and IP masquerading on a Linux host that runs Rocky Linux 9. The host has two network interfaces: eth0 and wg0.  Interface eth0 connects to the outside network and is assigned an public IP address while interface wg0 is on a private network. The objective is to make the Linux host as router for the private network so that the traffic originated from the private network can go to the outside network. The steps to achieve this objective using firewalld are as follows:

  1. Enable IPv4 forwarding
          echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf
          sudo sysctl -p
        
  2. Assign interface eth0 to the external zone
         firewall-cmd --permanent --zone=external --change-interface=eth0
        
  3. Assign interface wg0 to the internal zone
         firewall-cmd --permanent --zone=internal --change-interface=wg0
        
  4. Set the zone target of the internal zone to ACCEPT
         firewall-cmd --permanent --zone=internal --set-target=ACCEPT
        
  5. Finally, reload firewalld's configuration.
         firewall-cmd --reload
        

There is no need to meddle with anything else, such as adding nftables rules and set masquerading for the outward facing network interface. This is because the external zone is by default with masqerading enabled. This can be verified by

firewall-cmd --zone=external --query-masquerade
    

or by looking at the zone definition file at /usr/lib/firewalld/zones/external.xml.

In addition, the external zone's is also enabled to forward packets. We can examine this by looking at the zone definition file at /usr/lib/firewalld/zones/external.xml or by

firewall-cmd --zone=external --query-forward
    

The issue seems to lie at the zones' targets. First, let's view the zones' configuaration::

firewall-cmd --zone=external --list-all
    

Of course, we can also just check the target:

firewall-cmd --permanent --zone=external --get-target
    
firewall-cmd --zone=internal --list-all
    

Of course, we can also just check the target:

firewall-cmd --permanent --zone=internal --get-target
    

The targets of the both external and internal zones are both originally default. The internal zone's default target is in fact interpreted as reject, thus, preventing from packet forwarding to the outside network. This is explained as

For a forwarded packet that ingresses zoneA and egresses zoneB:
  • if zoneA's target is ACCEPT, DROP, or REJECT then the packet is accepted, dropped, or rejected respectively.
  • if zoneA's target is default, then the packet is accepted, dropped, or rejected based on zoneB's target. If zoneB's target is also default, then the packet will be rejected by firewalld's catchall reject.

Since both ingress (internal) and egress (external) are both "default", the result is that the internal zone's target becomes REJECT".

One question, I have in mind is, why do I not assign the internal facing interface to the trusted zone? That might be for another day.

Reference

This note benefited tremendously from the following resources:

  1. https://askubuntu.com/questions/1463093/what-is-target-default-of-a-zones-configuration-in-firewalld
  2. https://github.com/firewalld/firewalld/issues/590#issuecomment-605200548
  3. man firewall-cmd
  4. man firewalld.zone
  5. man firewalld