Skip to content

Latest commit

 

History

History
1918 lines (1372 loc) · 48.3 KB

File metadata and controls

1918 lines (1372 loc) · 48.3 KB

GIT Notes

These are the notes I update as I learn more about GIT source code revision control. Though not a beginner tutorial, I try to make these notes a useful resource for beginners as well as myself.

Git vs GitHub & GitLab

Don't conflate GIT with the GitLab or GitHub hosting services. There is no such thing as a "pull request" or "forking" in GIT. These are GitHub & GitLab constructs. GIT is design to clone repositories and push & pull changes between those repos that share past history.

Decentralization

GIT is decentralized. Unlike version control tools like SCCS, CVS, or Subversion there is no central or blessed repository, unless you just consider one as such. Even then the blessed repo is still just a social construct, GIT does not care.

GIT branches are just light weight user controlled labels, not large monolithic directory structures in a centrally maintained software repository.

In the 1990's, as a contractor for AT&T Microelectronics, I was the SCCS administrator for AT&T Microelectronics Process Control Manufacturing Execution System which tracked and controlled products through multiple semiconductor manufacturing production lines. Process Control, a misnomer, Production Control would have been a better name, controlled production based on customer orders and inventory. If it had existed, GIT would have been much simpler to configure, maintain and use than SCCS.

Git user interfaces

You do not know GIT until you can use its CLI interface. GIT's CLI interface was designed to implement user version control workflows. Typically I find that most GIT GUI clients, web interfaces and IDEs are designed to adapt the user to some particular workflow, typically that of the tool designer. Without a knowledge of GIT's CLI interface, it is much more difficult to know what to look for in a particular GIT GUI client's menus, keyboard shortcuts, and help utility. GUI and web clients come and go, GIT's CLI interface will be with us for decades, if not centuries.

Useful resources to learn GIT:


Getting help

Most Linux distributions have a good GIT tutorial in the man pages.

     $ man gittutorial

To get a comprehensive overview of git, use

    $ man git

and to get information on individual commands

    $ man git-add
    $ man git-info

or using the --help option in git

    $ git --help
    $ git clone --help

Three "patterns" to best leverage the above help commands

    $ git help verb
    $ git verb --help
    $ man git-verb

Also see GIT help files on the web.


GIT config command

Five places git stores config info

  • /etc/gitconfig
  • /usr/local/gitconfig
  • ~/.config/git/config
  • ~/.gitconfig
  • .git/config at root of repo

Each one overrides the ones above it.

The git config command (without --global)

  • updates the repo's '.git/config' file,
  • or complains if not in a GIT repository

The git config --global command will

  • update ~/.gitconfig if it exists
  • otherwise create and update ~/.gitconfig if ~/.config/git/config does not exist
  • otherwise update ~/.config/git/config

I usually keep ~/.config/git/config under git control and ~/.gitconfig not. That way git config --global configurations don't get clobbered when I update my config files with my dotfile installation scripts.

Aside: The git maintenance command will create ~/.gitconfig even if ~/.config/git/config exists.

Configuring GIT

Set up your identity

    $ git config --global user.name 'John Doe'
    $ git config --global user.email john.doe.2@us.af.mil

If you want to override these settings for a specific GIT repo, run the above commands without the --global option while in that repo's directory structure.

To customize editor

    $ git config --global core.editor nvim

To list the settings set so far

    $ git config --list

To list a specific setting

    $ git config user.name

What currently is configured for this project's repo

    $ cd ~/devel/notes/git-notes
    $ git config --list --show-origin --show-scope
    system	file:/etc/gitconfig	filter.lfs.required=true
    system	file:/etc/gitconfig	filter.lfs.clean=git-lfs clean -- %f
    system	file:/etc/gitconfig	filter.lfs.smudge=git-lfs smudge -- %f
    system	file:/etc/gitconfig	filter.lfs.process=git-lfs filter-process
    global	file:/home/grs/.config/git/config	user.name=grscheller
    global	file:/home/grs/.config/git/config	user.email=geoffrey@scheller.com
    global	file:/home/grs/.config/git/config	user.signingkey=~/.ssh/id_ed25519_grscheller.pub
    global	file:/home/grs/.config/git/config	gpg.format=ssh
    global	file:/home/grs/.config/git/config	core.editor=nvim
    global	file:/home/grs/.config/git/config	core.fsmonitor=false
    global	file:/home/grs/.config/git/config	core.pager=nvim -R -c 'set filetype=git'
    global	file:/home/grs/.config/git/config	color.pager=no
    global	file:/home/grs/.config/git/config	pull.rebase=false
    global	file:/home/grs/.config/git/config	init.defaultbranch=main
    global	file:/home/grs/.config/git/config	submodules.recurse=true
    global	file:/home/grs/.config/git/config	diff.submodule=log
    global	file:/home/grs/.config/git/config	rerere.enabled=true
    local	file:.git/config	core.repositoryformatversion=0
    local	file:.git/config	core.filemode=true
    local	file:.git/config	core.bare=false
    local	file:.git/config	core.logallrefupdates=true
    local	file:.git/config	remote.origin.url=git@github.com:grscheller/git-notes
    local	file:.git/config	remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
    local	file:.git/config	branch.main.remote=origin
    local	file:.git/config	branch.main.merge=refs/heads/main

GIT clone and init commands

Setting up your own GIT repository to work with.

Starting from scratch

Initializing a repository in an existing directory

    $ cd /path/to/an/existing/directory
    $ git init

This creates a .git subdirectory (a GIT repository skeleton).

Add files to this repository:

    $ git add *.c *.py
    $ git add LICENSE README.txt
    $ git commit -m 'initial project version'

Cloning existing repositories:

To clone an existing GIT repository

    $ git clone git@github.com:grscheller/grok-typescript
    $ git clone https://github.com/grscheller/grok-typescript learn-ts
    $ git clone grs@us.navo.hpc.mil:proj/grsHome.git
    $ git clone ~/devel/myRepo ~/junk/myRepoCopy

This is not just a snapshot from the repo of the project, it clones the entire repository, complete with all change control files, containing all past versions of the project. The second version changes the name of the directory containing the GIT repo from grok-typescript to learn-ts. GIT does not care about the name of the directory it gets cloned into. For the last version, the "upstream repo" or origin was set to "/home/grs/devel/myRepo" with no knowledge of the upstream origin, if it even exists.

Using GitHub or GitLab

Go to github.com or gitlab.com and create yourself an empty GIT repo for your project, using their webtools. Then clone the empty repo as shown in the above system. Both services provide useful boiler plate for various types of projects and software licenses.

Setting up a local shared repository

I found doing this useful when I had two contractors working the same code base but didn't have access to each other's GIT repos. The only thing they share is ssh access to the same out-of-date CentOS Linux system where I had no root access.

First I had to ask the system admin "nicely" to have the contractors and myself put in the same Linux secondary group with access to some common file system real estate. At least this admin knew what a "secondary group" was.

    $ su -
    $(root) groupadd repo --users grs dude1 dude2
    $(root) mkdir /share/repos
    $(root) chown grs:repo /share/repos
    $(root) chmod 2770 /share/repos

Then using my login

    $ umask 0007
    $ls -ld /share/repos
    drwxrws---. 7 grs repos 4096 Dec 12 09:17 /share/repos
    $ cd /share/repos
    $ git init --bare OurProject.git

This initializes an empty GIT repository, but without the outer working directory. Shared repositories should always be created with the --bare flag. Think --bare as marking the repository as a storage facility as opposed to a development environment.

Now connect up our empty naked repository to somewhere where some prior GIT based development work has been done, but neither contractor has access:

    $ cd OurProject.git
    $ git remote add upstream /home/grs/devel/someProject
    $ git fetch upstream
    $ git fetch --tags upstream

This will bring in all the branches and tags. Next from the someProject upstream repo

    $ cd /home/grs/devel/someProject
    $ git remote add downstream /share/repos/ourProject.git

Make sure your umask is set to 0007 so that all the files in ourProject.git get created with the necessary group rwx permissions for group access. If not, GIT will function, but with subtly bizarre behavior. The SGID bit is set on the directory so that all files will be in the repo group, not a user's default groups.

Both contractors can clone this shared repo and collaborate by pulling and pushing to it. I can pull from the shared repo and send upstream from my repo.

Changes I would change if I had to do this again:

  • refuse to help unless I had administrator privileges
  • run an actual git server either locally or somewhere else
  • have the contractors digitally sign their commits

If you are starting a brand new project, setting up the shared repo is a bit easier. Create a shared, empty, bare repository as above, clone it, add the initial files to the clone, and push it back to the shared repo.

    $ git clone /share/repos/ourProject.git
    $ cp -R /path/to/some/initial/files/* ourProject
    $ cd ourProject
    $ git add *
    $ git commit
    $ git push

Renaming the directory a local GIT repo is in

To "rename" the directory that a GIT repository is in, first note that that git does not care about the name of the working directory. The client just needs to know where to point.

On the server:

    $ mv PAT.git SDT.git

On the client:

    $ git remote rm origin
    $ git remote add origin grs@us.mhpcc.hpc.mil:projects/SDT.git

Note: you may need to tell a branch what its upstream now is

    $ git checkout master
    $ git branch -u origin/master
    $ git fetch origin

Cloning from another working repo.

Let's say I have a working repo in ~/Devel/SDT

    $ cd ~/Devel/SDT

Make sure we are in a stable state

    $ git status
    On branch sdt_devel_branch
    nothing to commit, working tree clean

See what branches are here

    $ git branch
      sdt_production_branch
    * sdt_devel_branch
      master

Let's clone the repo

    $ cd ../..
    $ mkdir temp
    $ cd temp
    $ git clone ../Devel/SDT
    Initialized empty GIT repository in /home/grs/temp/SDT/.git/

Let's see what we got

    $ cd SDT
    $ git branch
    * sdt_devel_branch
    $ git remote -v
    origin    /home/grs/temp/../Devel/SDT (fetch)
    origin    /home/grs/temp/../Devel/SDT (push)

We only picked up the currently active branch. I will sometimes do this to have a quick and dirty snapshot of a working copy of the software. Also, I can 'rm -rf .git' and either tar ball or burn to DVD what I want to give someone. Just make sure you do this in ~/temp/SDT and not in ~/Devel/SDT!!! I learned this the hard way.

Pushing a new branch to an empty upstream repo

Suppose you create an empty (no branches) repo on GitHub. Then clone it.

    $ git clone git@github.com:grscheller/experimental.git
    $ cd experimental

Git will complain that you cloned an empty repo.

Do some work ... and push to GitHub.

    $ git add .
    $ git commit -S
    $ git branch --list|cat
    * main
    $ git push -u origin main

Now upstream has a branch called main and it is the origin of your local main branch.


The fetch command

The fetch command updates local copies of remote branches.

    $ git fetch origin

will update information of local copies of all remote branches.

    $ git fetch --all

will do this with all remote repositories your local repo knows about.

Note: 'git fetch' commands will not create local branches to track remote branches. You must do a 'git checkout' for each new branch you want to track. The 'git pull --all' command will only pull from branches you currently track.


Overiding global configurations when cloning a repo:

To overiding global configurations when cloning a repo

    $ git clone --config user.email=me@myotheremail.com \
        --config http.sslcainfo=/home/geoff/.ssh/Cert_dropbox_wont_have.crt \
        --config http.verify=true \
        https://aur.archlinux.org/dropbox.git

You can use -c instead of --config.


Listing the commit comments made in a repo

To list the commit comments made for the current "checked out" version of the repo you are currently in, do

    $ git log

for a given director in the repo, do

    $ git log grok/Haskell

For a given file, do

    $ git log adminLogs/gauss17ArchLinuxAdmin.log

GIT Basics:

Here is the usual "state transitions" of files in a GIT workspace

Deleted  Untracked            Unmodified             Modified              Staged
  ∥        ∥                      ∥                     ∥                     ∥
  ∥        ∥---git add------------∥---------------------∥-------------------->∥
  ∥        ∥                      ∥---edit file-------->∥                     ∥
  ∥        ∥                      ∥                     ∥---git add---------->∥
  ∥        ∥                      ∥<--------------------∥--------git commit---∥
  ∥<-------∥-------------git rm---∥                     ∥                     ∥
  ∥        ∥                      ∥                     ∥                     ∥
  ∥        ∥<---git rm --cached---∥                     ∥                     ∥
  ∥        ∥<---------------------∥---git rm --cached---∥                     ∥
  ∥        ∥<---------------------∥---------------------∥---git rm --cached---∥
  ∥        ∥                      ∥                     ∥                     ∥
  ∥<-------∥----------------------∥---------git rm -f---∥                     ∥
  ∥<-------∥----------------------∥---------------------∥---------git rm -f---∥

And here is the "life cycle" of a GIT workflow

remote         tracked      local         index      workspace
  ∥              ∥            ∥             ∥              ∥
  ∥---pull-------∥------------∥-------------∥------------->∥
  ∥  |-----------∥----------->∥             ∥<-------add---∥
  ∥  |---------->∥            ∥             ∥              ∥
  ∥              ∥            ∥             ∥              ∥
  ∥---fetch----->∥            ∥<---commit---∥              ∥
  ∥<-------------∥-----push---∥             ∥              ∥
  ∥              ∥      |     ∥             ∥              ∥
  ∥              ∥<-----|     ∥             ∥              ∥

Checking status of files:

To checking status of the files in your repo

    $ git status
    $ git status --short

Note: Git only tracks files. not directories.

Begin tracking files

To begin tracking a file

    $ git add myfile.c    # myfile.c is now staged
    $ git add directory/  # all files in directory/ staged recursively

Think of add as meaning "add this content to the next commit" rather than "add this file to the project." GIT stages the file as it was when 'git add' command was issued. If you modify a file after it was staged, you have to add it again to pick up the latest changes.

Unstaging files

To unstage a file

    $ git restore --staged some_file

This s a clear improvement over how this was done in older versions of GIT as shown below.

    $ git reset HEAD some_file

HEAD is a special ref that points to the commit that currently is checked out. Using the git-restore command makes it more clear as to what is being done. The git-reset command seems to be taking advantage of GIT implementation details and is opaque to what is really being done.

To discard changes not already staged

    $ git checkout some_file

Ignoring files:

Use a .gitignore file to make GIT ignore files.

Example .gitinore file:

    # ignore all .a files
    *.a
    # but do track lib.a, dispite ignoring all .a files above
    !lib.a
    # only ignore the root TODO file, not subdir/TODO
    /TODO
    # ignore all files in the build/ directory
    build/
    # ignore doc/notes.txt, but not doc/server/arch.txt
    doc/*.txt
    # ignore all .txt files in the doc/ directory tree
    doc/**/*.txt
  • Blank lines and lines starting with # are ignored
  • Extended shell globbing patterns work
  • End patterns with a forward slash (/) to specify a directory
  • Negate a pattern by starting it with an exclamation point (!)
  • Two asterisks match nested directories

Moving, removing, and adding files.

Removing files

To remove files from a branch

    $ git rm file1 file2 dir1/
    $ git commit

If there are untracked changes to the files, the files will remain in the workspace as untracked files. GIT does not track directories so dir1/ will remain too if there are untracked files in it.

You may need to do

    $ rm file1 file2
    $ rm -r dir1/

To actually get rid of them from the working directory. Sometimes one run into empty directories when files are removed upstream.

Lets say you want to remove lots of files. For example, lets get rid of all the subversion directories (since we are using GIT).

    $ find . -depth -name '.svn' -exec rm -rf '{}' \;

Best practice is to then use

    $ git add --update
    $ git commit

The --update or -u option only matches files in the index rather than the working tree. This removes as well as modifies index entries to match the working tree, but adds no new files.

This will save you from having to do a lot of tricky bash shell scripting like

    $ git rm $(git status | grep delete | awk '{print $3}')
    $ git commit

Which will also work.

To updating major changes, such as a vendor upgrade, with file additions and removals, in a brute force sort of way, use

    $ git add --all
    $ git commit

The --all or -A option is like --update except that it also will match against files in the working tree.

For better fidelity, especially when dealing with directory structure changes, one might want to use git mv commands.

Moving files

To move files

    $ git mv path/to/filename new/path/to/new_filename
    $ git commit

GIT will move and stage the original version. If you move a file with unstaged changes, The changes made to the file will be unstaged changes within the working directory.

Usual Unix mv command semantics apply. To move a bunch of files to a new directory

    $ mkdir new_dir
    $ git mv file1 file2 file3 new_dir
    $ git commit

To remove an empty directory.

GIT does not track directories, only files. You can't use "git rm" to remove a directory and push this change. See Cleaning up your working tree in the Git Maintenance section below,

Adding files to track

To add files recursively down from the current directory,

    $ git add .

This will also remove files that have been deleted.

To only update tracked files, use

    $ git add --update .

This will prevent "random clutter" from being tracked.


Git Maintenance

Cleaning up your working tree

The git clean command takes the following options

Option Description
-d remove untracked directories too
-n dry run, just show what to remove
-f force
-i interactive mode
-x don't use ignore rules from .gitignore files
-X remove only files ignored by git
-e pat add pattern pat to ignored patterns from .gitignore

Where one of -n, -f, or -i must be selected.

To see what needs cleaning up,

    $ git clean -dn

Then either manually clean up or use

    $ git clean -df

This won't work if there are .gitignore files in these directories. To fully clean things up, use

    $ git clean -fdxn  # so you don't shoot yourself in the foot
    $ git clean -fdx

Creating revisionist history via git rebase:

Rewriting your history, don't do this if you already have shared it!!!

The 'git rebase' command comes in handy when you need to reorder commits, change commit messages, squash commits together.

A reasonable reason for squashing commits might be to enforce a policy of never pushing non-working history to a release branch. On the other hand, squashing commits puts changes into chunks which may be too large. This will make tools like git bisect much less effective, making it more difficult for end user bug reports to pinpoint when and where the code got broken.

Hiding the scaffolding and refactoring you used to get to the code to its current state of affairs is never a good reason to rebase.

Redo the last 4 commits:

    $ git rebase -i HEAD~4

Drops you into an editor session:

    pick 8c4a6a5 Commit message four commits ago.
    pick 4a3f436 Commit message three commits ago.
    pick 949e05d Commit message two commits ago.
    pick 8ae51b6 Commit message on last commit.
    ...

And gives you the following choices to edit into above:

    p, pick = use commit
    r, reword = use commit, but edit the commit message
    e, edit = use commit, but stop for amending
    s, squash = use commit, but meld into previous commit
    f, fixup = like 'squash', but discard this commit's log message
    x, exec = run command (the rest of the line) using shell
    d, drop = remove commit

In the case of a merge conflict, GIT drops you back to a shell. Fix the conflict, and

    $ git add ...
    $ git rebase --continue   # or use "git rebase --abort"

Amending commits

If you have not pushed your changes upstream, you can use the 'git commit --amend' to update the last commit. GitHub will refuse a push with such an amended commit if a previous version was already pushed. You will need to do a git merge with the upstream version to make your HEAD pushable.

Amending pushed commits

You just pushed to GitHub changes you very deeply regret. GitHub rejects your amended changes.

You don't want to be a bad boy and cause grief to others by doing a

    $ git push --force   # DON'T Do THIS!!!

The above could swallow work done by others!

There is a newer option to the git push command that may save you.

    git push --force-with-lease

When this option is used, GIT will let you force commit this change as long as it will not overwrite any work on the remote branch when more commits were added to the remote branch.

You did not have to do a messy merge! But you may still be a bad boy. If someone had fetched your changes they will not be able to push back until they did a messy merge. Also, until they fully merge, they will still have access to your dirty little secret and could very well put it back!

There is a lot more to this command and could be useful in rebasing situations. This option can take a ref as an optional parameter. This option allows you to say that you expect the history you are updating is what you rebased and want to replace. If the remote ref still points at the commit you specified, you can be sure that no other people did anything to the ref. It is like taking a "lease" on the ref without explicitly locking it, and the remote ref is updated only if the "lease" is still valid.

--force-with-lease alone, without specifying the details, will protect all remote refs that are going to be updated by requiring their current value to be the same as the remote-tracking branch we have for them.


GIT branch:

Note that all branches really are just pointers to commits.

Create new branches

To create a new branch (does not checkout the branch)

    $ git branch myBranch

This branch is based on the current branch checked out.

To create a new local empty branch

    $ git checkout --orphan newBranch

This will create a new branch without any commits. The first commit will start a new history without any ancestry. The --orphan is a useful option when you want to create a directory structure and files resembling the ones from the current branch. Also when you want to create something related to the effort, like documentation or a software tool useful to the main project.

List available branches

List all available local branches:

    $ git branch

To also see the remote tracked branches:

    $ git branch -a
      Anansi
      astrolog-java
      bar-mph
      silverdb
      silver_grs_oneToolbar
    * master
      oneToolbar
      remotes/origin/Anansi
      remotes/origin/HEAD -> origin/master
      remotes/origin/astrolog-java
      remotes/origin/bar-mph
      remotes/origin/silverdb
      remotes/origin/silverdb_grs_oneToolbar
      remotes/origin/master

View local branches relative to current branch

To view merged local GIT branches relative to current branch

    $ git branch --merged

And unmerged local GIT branches

    $ git branch --no-merged

Delete a local branch

To delete a local branch safely, git will prevent you if you have unmerged changes.

    $ git branch -d myBranch

To force delete a specified branch and permanently throw away all unmerged changes

    $ git branch -D myBranch

Delete a remote branch

To delete a remote branch or tag

    $ git push origin --delete myBranchToDelete

Prior to git version 7.0 but will still work

    $ git push origin :myBranchToDelete

Deleting remote references

To delete references to remote branches that no longer exist on their remote repos.

    $ git fetch -p
    $ git fetch -p some_remote_repo
    $ git fetch -p origin

Renaming branches

To rename a local Branch

    $ git branch -m oldname newname

To rename the current branch to new name

    $ git branch -m myNewName

Deleting tracking banches

To delete an existing (local) remote tracking branch

    $ git branch -d -r origin/macos

Tracking info

To figuring out what exactly your branches are actually tracking

    $ git branch -vv
    car-mhf         b819996 ,origin/car-mhf, Fixed awkward language in a comment to make more clear.
    goldsdb         41d7446 [origin/goldsdb] The initial View Table now contains 1 empty row.
    master          28f7355 [origin/master] Up dates Resources/DTIC_GIT_Notes.txt with notes on:
    * scheller-master 2c7336f [scheller/master: ahead 1] An updater to the root README.md file.

    $ git remote -v
    origin  https://geoffrey.scheller@repos.vdl.afrl.af.mil/git/astrodynamics/astrodynamics.git (fetch)
    origin  https://geoffrey.scheller@repos.vdl.afrl.af.mil/git/astrodynamics/astrodynamics.git (push)
    scheller    ../../../scheller-linux-archive/ (fetch)
    scheller    ../../../scheller-linux-archive/ (push)

GIT checkout:

The git checkout command lets you navigate between branches. Checking out a branch updates the files in the working directory to match the version stored in that branch, and it tells GIT to record all new commits on that branch. Think of it as a way to select which line of development you’re working on.

Checking out branches

    $ git checkout existingBranch

Can also create a new branch at checkout

    $ git checkout -b newBranch

Or base it on another existing branch instead of the current one.

    $ git checkout -b newBranch anotherBranch

You can work on multiple branches in a single repository by switching between them with git checkout or git switch.

Check out a branch you don't have locally from origin (the repo you are tracking from, usually the one you first cloned from).

    $ git fetch
    $ git checkout some_new_remote_branch

If you are tracking several remote branches, you may need to be a bit more specific:

    $ git checkout -b some_branch remote-name/some_branch

Lets say we have two remotes each with a branch with same name

    $ git branch -r
    origin/copperDB
    origin/dmc_run
    origin/master
    origin/spaceRad
    scheller/master

and we are already tracking origin/master.

    $ git branch
      copperDB
      dmc_run
    * master

How do we checkout scheller/master? We give it another name

    $ git checkout -b scheller-master --track scheller/master

Now we have

    $ git branch
      copperDB
      dmc_run
      master
    * scheller-master

GIT merge:

The git merge command is used to merge another branch into your current branch. If merge successful without conflicts, you are done. If not, changes are merged into your working directory with GIT putting comments in the code. In this case, you still need to resolve the conflicts and do a git commit.

Merging in another branch

To merge a specific branch into the current branch

    $ git merge someBranch

Note: The branch you are merging into is the currently checked out branch. If you are in a "detached HEAD" state, you will need to create a branch to merge someBranch into.

Edit any conflicts, git add changes, commit changes if needed.

Three-way merge

Here is an example of the workflow for a 3-way merge. Start a new feature on its own new branch

    $ git checkout -b new-feature master

edit some files

    $ git add file1 file2 file3
    $ git commit -m 'Starting new feature X'

edit some more files

    $ git add file2 file4
    $ git commit -m 'Finish new feature X'

Parallel development on the master branch

    $ git checkout master

edit some files, file3 changes somewhat orthogonal to feature X changes

    $ git add file3 file5
    $ git commit -m 'Make some super-stable changes to master'

Merge in the new-feature branch

    $ git merge new-feature
    $ git commit

Delete the new-feature branch once things are safely tested and merged, no sense keeping old cruft around. Best practices is to only push to origin only stable changes.

    $ git branch -d new-feature

Periodically incorporate changes from master:

Start a new feature

    $ git checkout -b long-term-feature master

Do some development for a while ...

Switch to master and sync up with origin/master

    $ git checkout master
    $ git pull origin master

Switch back to development branch and merge in changes from master

    $ git checkout long-term-feature
    $ git merge master
    <edit any conflicts, 'git add' changes, commit>

Continue long-term development ...


Syncing repositories:

Making connections

To list remote connections.

    $ git remote
    origin

Use -v for more info.

    $ git remote -v
    origin grs@us.navo.hpc.mil:proj/PAT (fetch)
    origin grs@us.navo.hpc.mil:proj/PAT (push)

The name, this case origin, can be used as a short cut to the other repository in git commands. The name origin is the default name given to the reprository you clone from, otherwise there is nothing special about it.

To add a remote connection

    $ git remote add ethel emurtz@devel.desilu.com:repo/iHateFred

To remove a connection

    $ git remote rm fred

To rename connection

    $ git remote rename ricky lucy

Developers need to pull upstream commits to their local repository and push local commits to other repositories. Having connections to other individual developers makes it possible to collaborate outside of the main (or blessed) repository. This can be very useful for a small teams working on a large subproject.

Have existing branch track a remote branch

To make an existing branch track a remote branch, as of GIT 1.8.0

    $ git checkout bar
    $ git branch -u upstream/foo

Now your local branch bar is tracking the branch foo on the remote repo upstream.

If you are on a branch other than bar,

    $ git branch -u upstream/foo bar

if you like longer options, in 1.8.0+ you can do

    $ git branch --set-upstream-to=upstream/foo bar

To keep things simple, best practices is to rename upstream to origin or origin-foo and rename your local branch bar to foo.

Use GIT fetch to sneak a peak

Use fetch to import commits into the tracking branch for the remote repository. This gives you a chance to review changes before integrating them into the local copy of the project.

    $ git fetch origin
    $ git diff origin

or with more fidelity

    $ git switch someBranch
    $ git fetch origin someBranch
    $ git diff origin/someBranch

Use git branch to view local branches and use git branch -r for remote branches. Inspect these branches with the ususal git chechout and git log commands. Be aware that checking out a tracking branch will leave you in a "detached head" state.


Push and Pull

Use git push to merge local changes into remote branches located on remote repos.

Use git pull to merge in remote changes. This command syncs the local tracking branch with the remote branch. Then it merges the local tracking branch into the local branch. The git pull command is shorthand for git fetch followed by git merge FETCH_HEAD

Remember, pushing and pulling are between different repositories. Merging happend between branches on the same repository. Both git push and get pull use git merge behind the scenes.

GIT push and pull

    $ git pull origin someBranch

To pull from the branch you are tracking with the current branch, use

    $ git pull

Warning!!!

     $ git pull origin someBranch

will pull in from the remote "someBranch" even if you are on the local "someOtherBranch".

Best practice is:

    $ git fetch origin  # So local repo knows of remote changes.
    $ git status        # So we are sure we are where we think.
                        # This will also give other potentially
                        # useful info to keep yourself from
                        # shooting yourself in foot.
    $ git pull

Now, git status may tell us we can't just do a fast foreword. In that case, create a new local branch tracking the remote branch. That way you have two places to morph before doing a git merge.

The command

    $ git pull

which actually is the same as

    $ git fetch
    $ git merge FETCH_HEAD

which accomplishes same thing as doing

    $ git fetch origin         # Sync local copy of master
                               # with master on origin.
    $ git merge origin/master    # Merge local copy from last
                                 # fetch from origin into your
                                 # local version of master.

or, assuming master tracks origin/master

    $ git fetch
    $ git merge origin

What you are actually merging into your working directory is a local copy of the remote repo. You are "tracking" the local copy. If pull requires a password, then the fetch will too, but not the merge. The git fetch is syncing your local copy with what is on the remote.

More explicitly,

    git checkout master         # or whatever branch
    git fetch origin            # fetch all tracked upstream changes
    git merge origin/master     # local copy of upstream branch

To push local changes elsewhere

    $ git push origin someBranch
    $ git push origin --all
    $ git push origin --tags

The commands (with nothing else)

    $ git push
    $ git push origin

should be avoided on older versions of GIT. On git 1.9.5, it will push all tracked branches to their remotes. In later versions of git, it will just push whatever branch you are currently on.

To remove a remote branch or tag, do

    git push --delete Remote_Name Branch_or_Tag_Name

On older versions of GIT

    $ git push Remote_Name :Branch_Name

Note the space before the colon, similar to renaming a branch, you are pushing "nothing" into BranchName.

To push a tag,

    $ git push <RemoteName> <TagName>

To push all tags

    $ git push <RemoteName> --tag

GIT push to GITHUB

Cloning from GITHUB sets the URL to origin as

    $ git config remote.origin.url
    https://github.com/grscheller/scheller-linux-environment

On Arch Linux 'git push' prompts for username and password and everything works fine. On CentOS 6.8 (git version 1.7.1) I get the error:

    $ git push
    error: The requested URL returned error: 403 Forbidden while accessing https://github.com/grscheller/scheller-linux-environment/info/refs
    fatal: HTTP request failed

To fix this, set the URL to

    $ git config remote.origin.url https://grscheller@github.com/grscheller/scheller-linux-environment.git

Not recommended, but setting it to https://grscheller:MYPASSWORD@github.com/grscheller/scheller-linux-environment.git would allow you to not have to type your password.

I think the issue is because of the really olde version of git that CentOS 6.8 uses, but it might also be related to firewall proxy issues.

Interesting factoid:

When I changed the name of a repo from scheller-linux-environment to scheller-linux-archive, above value for remote.origin.url still worked, but changing the repo name part of it to something random like, like scheller-linux-foofoo, failed.

Using SSH with GITHUB:

From the GITHUB website, go to Settings -> SSH and GPG keys

Paste contents of ~/.ssh/id_rsa to the SSH text box. Leave off system name at end. No newlines.

Next, from the repo, tell git to use ssh protocal

    $ git remote set-url origin git@github.com:grscheller/scheller-linux-archive
   Now,
    $ git remote -v
    origin     git@github.com:grscheller/scheller-linux-archive (fetch)
    origin     git@github.com:grscheller/scheller-linux-archive (push)
   Able to push to GITHUB without the password.

   I created a shell alias to restart the ssh-keyserver if it should
   ever get hung.
    $ alias addkey
    alias addkey='eval $(ssh-agent) && ssh-add'

Tagging:

List available tags.

To list all current tags in alphabetical order

    $ git tag

To refine the search

    $ git tag -l 'v1.8.*'
    $ git tag -l '*foo*'

Creating Tags.

There are two types of tags, lightweight and annotated. A lightweight tag is like a branch that does not change, just a pointer to a specific commit. Annotated tags are stored as full objects in GIT database. They have the tagger's name, e-mail address, date, and can be signed with GNU Privacy Guard.

To create an annotated tag, use -a

    $ git tag -a v1.0 -m 'Original DVS code gotten from David Steller'

Pushing tags

By default, git push does not push tags. To explicitly push a tag

    $ git push origin v1.0

To push all available tags, use the --tags option on git push

    $ git push origin --tags

Checking out tags.

To create a new branch at a specific tag, say v2.2.0

    $ git checkout -b version2-2 v2.2.0

Deleting tags (local and remote).

To delete local tag:

    $ git tag -d tag_to_delete

To delete off of a remote repo:

    $ git push origin :refs/tags/tag_to_delete

Show info about a tag

Use the show cmd

    $ git show tag_name

Examining previous versions of a file:

GIT show

Use 'git show' to view a previous version of file

    $ git show REVISION:path/to/file

for example

    $ git show HEAD~3:PAT_Files/mfiles/FilterValuesPanel.m

will send the third version back of the file to a pager.

    $ git show HEAD~1:pat.m > junk

will dump it all to a file called junk.

    $ git show HEAD@{2022-03-30}:./config/nvim/lua/grs/init.lua

will show the version of the file as of that date.

    $ git show HEAD@{2022-03-30}:./config/nvim/lua/grs|cat
    tree HEAD@{2022-03-30}:./config/nvim/lua/grs

    Colorscheme.lua
    Completions.lua
    DevEnv.lua
    Options.lua
    Packer.lua
    Telescope.lua
    TextEdit.lua
    Treesitter.lua
    WhichKey.lua
    init.lua

which lists the files in that directory as of that date.

    $ git show HEAD@{2022-06-4}

will list the commit messages and diffs for that day.

GIT log

Use 'git log' to view the revision history a of file

    $ git log -p --follow config/nvim/init.lua

The -p tells git to show all patch information, the --follow to follow the history even in the event that the file name was changed.


GIT Revision History Tools:

GIT log

To show commit comments for history

    $ git log

To shows history of both commits A and B (a Union)

    $ git log A B

To include diff info with commit comments

    $ git log -p

First line summaries of commit messages

    $ git log --oneline

To get the most out of git log, remember the seven rules of a great GIT commit message:

  • Separate subject from body with a blank line
  • Limit the subject line to 50 characters
  • Capitalize the subject line
  • Do not end the subject line with a period
  • Use the imperative mood in the subject line
  • Wrap the body at 72 characters
  • Use the body to explain what and why vs. how

Above taken from this blog post.

GIT diff

Let's say we have three branches, main, feature1 and feature2.

First, lets see how to compare two specific files in two specific branches against each other. Output will be similar to the Unix diff command.

    $ git diff feature1:file1 feature2:file2

The line between the files which are the same will be colored white, red lines will be the items in file1 not in file2, green lines will be for items in file2 not in file1. Left red, Right green. The red lines will begin with a -, the green lines a +.

Compare two branches. If you leave off the :, git usually guesses correctly what you mean. I like to be non-ambiguous.

    $ git diff main: feature1:

Comparing past version with what is in the working directory

    $ git diff HEAD~2:DVS.m DVS.m

Note that 2 revisions ago need not necessarily be on the same branch!

To comparing a file between branches

    $ git diff new_feature:DVS.m master:DVS.m

If given only one argument, compare with the working directory.

GIT Ranges

The range A..B will be resolved to show each commit individually from A to B. Files can be specified via syntax commit:path/to/file. Pass it a directory, it will show info of the last commit changing in that directory.

git range

The range A...B means every commit reachable by A or B but not both.

  • for git log will show commits it makes when used with divergent branches
  • for git diff it is syntactic sugar for git diff $(git merge-base A B) B
  • for git show will show commit info for each single commit in that range

git merge-base

Find the best common ancestor for a three way merge.

Find all the "roots" of a repo,

    $ git rev-list --max-parents=0 HEAD
    4a0c3cff3b6d192effeb44bc2d8429c5e9f85825
    6e886a1b01a10f39b53bf8b90dba4c73625f4353

Finding the first commit a file appeared

    $ git log --oneline -- Actor.scala | tail -n 1
    d71adde Implemented book's nonblocking fpinscala.parallelism package.
    Blocking version now package fpinscala.parallelism.javaFutures.

Then search for equivalent of "d71adde" in a git log.


Using Neovim as GIT Pager:

You need to turn color off in GIT and let nvim do colorization.

    $ git config --global color.pager no
    $ git config --global core.pager "nvim -R -c 'set filetype=git'"

The -R means read-only.


GIT Stash:

Store away current state of working directory and the index and goes back to a clean working directory:

    $ git stash push

or just

    $ git stash

Drop the last stash entry from the list of stash entries:

    $ git drop

Pop changes back into working directory, perhaps on a subsequent commit.

    $ git stash pop

This may fail due to conflicts due to changes being applied at the commit that was HEAD at the time of the stash. Resolve the conflict and use git drop.

Remove all stash entries:

    $ git stash clear

Note, entries will be subject to pruning, so there may be no way to recover these changes.

Typical workflow when following a remote repo:

    $ git stash
    $ git pull
    $ git stash pop

TODO:

TODO: Merge the following in the following

To create a temporary branch and push it upstream.

    $ git branch tmpWork
    $ git stash
    $ git checkout tmpWork
    $ git stash apply             # stash apply ???
    $ git add .
    $ git commit
    $ git push -u origin tmpWork
    $ git status
    On branch tmpwork
    Your branch is up to date with 'origin/tmpWork'.

    nothing to commit, working tree clean

TODO: Explain the difference between git checkout branch and git switch branch. The first is a bit overloaded.

TODO: Add info on git blame

TODO: Add info on rerere https://git-scm.com/docs/git-rerere https://git-scm.com/book/en/v2/Git-Tools-Rerere

TODO: Add info on git maintenance

TODO: Add info on git diff --cache

TODO: Look into git-rev-list and git-show-branch

TODO: Give examples of Git Ranges in use

TODO: Update GitHub access

TODO Git reset and ~ vs ^ (time moves down)

        G   H   I   J
         \ /     \ /
          D   E   F
           \  |  / \
            \ | /   |
             \|/    |
              B     C
               \   /
                \ /
                 A

        A =      = A^0
        B = A^   = A^1     = A~1
        C = A^2
        D = A^^  = A^1^1   = A~2
        E = B^2  = A^^2
        F = B^3  = A^^3
        G = A^^^ = A^1^1^1 = A~3
        H = D^2  = B^^2    = A^^^2  = A~2^2
        I = F^   = B^3^    = A^^3^
        J = F^2  = B^3^2   = A^^3^2

Note that git-merge can join two or more development histories together.