Skip to content

workflow

Mark Gates edited this page Jul 13, 2023 · 1 revision

[TOC]

Suggested Configuration

Configure with your work email, per first-time Git setup:

> git config --global user.name "John Doe"
> git config --global user.email [email protected]

To prevent unintended merges, configuring "fast-forward only" globally is highly recommended:

> git config --global pull.ff only

Having a graphical view of branches is indispensable. Ways to do this include:

Using a graphical diff for resolving merges is also invaluable. There are several tools available: meld (recommended), opendiff (macOS only), tkdiff, etc.

git diff        # prints diff in shell
git difftool    # uses graphical diff
git config --global diff.tool meld
git config --global difftool.prompt false

git mergetool   # uses graphical 3-way merge
git config --global merge.tool meld
git config --global mergetool.prompt false

Other useful git aliases and settings in ~/.gitconfig:

[user]
    name = John Doe
    email = [email protected]

[pull]
    ff = only

[alias]
    id  = rev-parse --short HEAD  # hash of current commit
    idx = log --oneline -n 1      # log of current commit
    st  = status
    ls  = ls-files
    co  = checkout
    branches = branch --all -v

    # Fast-forward a branch, master to origin/master after `git fetch origin`
    # Usage: git fwd master dev/master
    fwd  = "!git fetch . \"$2:$1\""

    mt   = mergetool             # for resolving merge conflicts
    meld = difftool -t meld -d

    # opendiff is graphical diff in macOS Xcode
    od   = difftool -t opendiff -d

    #                                                    hash,                 author-date,         author-name     subject  branches                          yyyy-mm-dd hh:mm
    glog = log --all --graph --decorate --format=format:'%C(yellow)%h%C(reset) %C(blue)%ad%C(reset) %C(red)%aN%C(reset)  %s  %C(bold)%d%C(reset)' --date=format:'%Y-%m-%d %H:%M'

# https://stackoverflow.com/questions/34119866/setting-up-and-using-meld-as-your-git-difftool-and-mergetool
[difftool "meld"]
    cmd = meld "$LOCAL" "$REMOTE"

[mergetool "meld"]
    # Choose one of these 2 lines (not both!) explained below.
    # Each is 3 panes; the middle is either the merged or the original base.
    cmd = meld "$LOCAL" "$MERGED" "$REMOTE" --output "$MERGED"
    #cmd = meld "$LOCAL" "$BASE" "$REMOTE" --output "$MERGED"

[mergetool]
    prompt = false

[color]
    ui = true

I tend to use ssh for accessing Bitbucket repos, as it eliminates repeatedly entering my password. Online docs cover this topic in more detail.

  1. Generate an ssh key on your computer using ssh-keygen. Copy the resulting public key from ~/.ssh/id_rsa.pub to Bitbucket > Personal settings > SSH keys, as shown below.

ssh keys

  1. [Optional] On ICL machines, you can add this public key to ~/.ssh/authorized_keys to avoid the need to enter your password every time you ssh saturn.

  2. Start ssh-agent, if needed. This is already done on macOS. Check $SSH_AUTH_SOCK. Otherwise, run (e.g., in ~/.profile or ~/.login):

     eval `ssh-agent -t 28800`   # 8 hours = 28800 seconds
    
  3. Use ssh-add to enter your password, adding that identity to the ssh-agent. Now you should not have to re-enter your password for every git push and ssh saturn.

  4. Bitbucket's Clone button has a menu to choose between SSH and HTTPS URLs:

clone

Workflow

There is a slate-dev fork for development. Do all development in a feature-specific branch, not in the master branch, to minimize conflicts.

Alternatively, instead of using the slate-dev fork, create your own fork of the main slate repo. This incurs some extra overhead in setting up Jenkins for your fork.

The purpose of having a fork instead of working in the main repo is to allow work to be rebased or abandoned. Once commits have been pushed into the main repo, they are public, cannot be rebased, and are not easily abandoned since other people may have cloned them.

To demonstrate, I created a simple workflow repo and a dev fork of it.

Checkout the dev fork. I prefer renaming "origin" to something descriptive.

> git clone --recursive [email protected]:mgates3/workflow-dev.git
> cd workflow-dev
> git remote rename origin dev

> git remote -v
dev     [email protected]:mgates3/workflow-dev.git (fetch)
dev     [email protected]:mgates3/workflow-dev.git (push)

Add link to main bitbucket repo, as desired.

> git remote add bitbucket [email protected]:mgates3/workflow.git

> git remote -v
bitbucket   [email protected]:mgates3/workflow (fetch)
bitbucket   [email protected]:mgates3/workflow (push)
dev         [email protected]:mgates3/workflow-dev.git (fetch)
dev         [email protected]:mgates3/workflow-dev.git (push)

At this point, say these commits exist:

> git glog
* 6d9716f 2020-10-27 14:33 Mark Gates  add b.txt   (HEAD -> master, dev/master, dev/HEAD)
* a507af6 2020-10-27 14:32 Mark Gates  add a.txt

Create a branch, to alleviate confusion about the local master branch diverging from the remote master branch:

> git switch -c my-branch

equivalent to:

> git branch my-branch
> git checkout my-branch

Commit changes:

> git add c.txt     # add new file
> git status -s     # check what is being committed
A  c.txt
> git commit -m 'add c.txt' c.txt

> git add d.txt
> git status -s
A  d.txt
> git commit -m 'add d.txt' d.txt

> git glog
* 0389be6 2020-10-27 15:25 Mark Gates  add d.txt   (HEAD -> my-branch)
* b6f68d4 2020-10-27 15:25 Mark Gates  add c.txt
* 6d9716f 2020-10-27 14:33 Mark Gates  add b.txt   (dev/master, dev/HEAD, master)
* a507af6 2020-10-27 14:32 Mark Gates  add a.txt

See docs for git status codes.

You can confirm what was committed:

> git show HEAD         # diff current ("HEAD") commit
> git show b6f68d4      # diff any commit by id

If anything was ommitted, the last commit can be ammended, which replaces it with a new commit. (Do not amend commits that have been published in the main repo!)

> git commit --amend d.txt

(Advanced: Editing commits further back in history is possible with, e.g., git rebase -i HEAD~5, where 5 is the number of commits to go back. Again, do not edit commits that have been published.)

Fetch updates from the main repo, with fast-forward only. The master pointer is moved ("fast-forward") to the latest commit. In this case, two commits (e and f) have been added since forking, causing my-branch and master to diverge:

> git checkout master
> git pull [--ff-only] bitbucket master

> git glog
* 0389be6 2020-10-27 15:25 Mark Gates  add d.txt   (my-branch)
* b6f68d4 2020-10-27 15:25 Mark Gates  add c.txt
| * 0ee2c41 2020-10-27 14:36 Mark Gates  add f.txt   (HEAD -> master, bitbucket/master)
| * f673e05 2020-10-27 14:36 Mark Gates  add e.txt
|/
* 6d9716f 2020-10-27 14:33 Mark Gates  add b.txt   (dev/master, dev/HEAD)
* a507af6 2020-10-27 14:32 Mark Gates  add a.txt

This git pull is equivalent to:

> git fetch bitbucket master        # downloads commits
> git merge [--ff-only] FETCH_HEAD  # merges those with current branch

Rebase

my-branch can be rebased onto master (preferred) or merged with master. Either will require resolving conflicts. To rebase my-branch onto master branch:

> git checkout my-branch
> git rebase master my-branch

> git glog
* e6428a4 2020-10-27 15:25 Mark Gates  add d.txt   (HEAD -> my-branch)
* e93b752 2020-10-27 15:25 Mark Gates  add c.txt
* 0ee2c41 2020-10-27 14:36 Mark Gates  add f.txt   (bitbucket/master, master)
* f673e05 2020-10-27 14:36 Mark Gates  add e.txt
* 6d9716f 2020-10-27 14:33 Mark Gates  add b.txt   (dev/master, dev/HEAD)
* a507af6 2020-10-27 14:32 Mark Gates  add a.txt

Note the commit ids for c and d changed.

If there is a conflict, you can either manually edit the conflicting files (ugh!), or use a graphical 3-way merge tool (recommended). See configuration above to choose merge tool.

> git rebase master
CONFLICT (add/add): Merge conflict in e.txt
...

> git status -s
AA e.txt

> git mergetool     # runs graphical merge tool; edit & save changes

> git status -s
M  e.txt

> git rebase --continue

# repeat `git mergetool` and `git rebase --continue` until all
# commits have been resolved.

Merge

Alternatively, you can merge the master into my-branch, to get the latest updates. (Merging my-branch into master should happen via a pull request.)

> git checkout my-branch
> git merge master

> git glog
*   7ce3197 2020-10-27 15:28 Mark Gates  Merge branch 'master' into my-branch   (HEAD -> my-branch)
|\
| * 0ee2c41 2020-10-27 14:36 Mark Gates  add f.txt   (bitbucket/master, master)
| * f673e05 2020-10-27 14:36 Mark Gates  add e.txt
* | 0389be6 2020-10-27 15:25 Mark Gates  add d.txt
* | b6f68d4 2020-10-27 15:25 Mark Gates  add c.txt
|/
* 6d9716f 2020-10-27 14:33 Mark Gates  add b.txt   (dev/master, dev/HEAD)
* a507af6 2020-10-27 14:32 Mark Gates  add a.txt

Note the commit ids for c and d remain the same, and a new merge commit is added.

Again, if there is a conflict, either resolve it manually, or use a graphical 3-way merge tool, then commit changes.

> git merge master
CONFLICT (add/add): Merge conflict in e.txt
Auto-merging e.txt
Automatic merge failed; fix conflicts and then commit the result.

> git status -s
AA e.txt
A  f.txt

> git mergetool     # runs graphical merge tool; edit & save changes

> git status -s
M  e.txt
A  f.txt

> git commit -m 'merge master into my-branch'

Undo rebase or merge

Note you can undo a rebase or a merge. Just checkout the original commit hash and force switch the branch back. Thus it's helpful to do git glog before doing a rebase, and temporarily saving the output until you're satisfied the rebase was good.

> git checkout -B my-branch 0389be6
Reset branch 'my-branch'

> git glog
* 0389be6 2020-10-27 15:25 Mark Gates  add d.txt   (HEAD -> my-branch)
* b6f68d4 2020-10-27 15:25 Mark Gates  add c.txt
| * 0ee2c41 2020-10-27 14:36 Mark Gates  add f.txt   (bitbucket/master, master)
| * f673e05 2020-10-27 14:36 Mark Gates  add e.txt
|/
* 6d9716f 2020-10-27 14:33 Mark Gates  add b.txt   (dev/master, dev/HEAD)
* a507af6 2020-10-27 14:32 Mark Gates  add a.txt

Push commits

Once satisfied, push changes to the dev fork:

> git push dev my-branch

and create a pull request (PR) on bitbucket. Changes can continue to be pushed to my-branch on the dev fork, which will update the PR. If commits have previously been pushed, but then you rebased those commits, you need to force push (but see cautions below!):

> git push -f dev my-branch

On Jenkins, check that the branch passes tests, and copy the Jenkins URL into the PR to show that it has passed.

Caution!

Do not rebase commits that other SLATE developers have started to use.

Do not rebase commits that have already been published in the main repo. Since others may have downloaded those commits, they are final.

Do not force push to the main repo. In fact, do not push to the main repo; use pull requests.

Also, please do not use Bitbucket Sync feature on the fork, shown below. Doing so adds needless and confusing merge commits, rather than simply fast-forwarding the master branch to the latest version. Use the above procedure to fast-forward.

not sync

Moving branch pointers

I find that I have to move branch pointers around from time to time. I'm really not sure if this is usual, or because I fight with git, but it happens. Just use one of the force commands:

To create and checkout a branch at the current commit:

# capital -C forces creating my-branch here, if it already exists.
> git switch -C my-branch

equivalent to:

# -f forces creating my-branch here, if it already exists.
> git branch -f my-branch
> git checkout my-branch

To create and checkout a branch at, say, commit 90a83cd:

# capital -B forces creating my-branch, if it already exists.
> git checkout -B my-branch 90a83cd

equivalent to:

> git checkout 90a83cd
> git branch -f my-branch
> git checkout my-branch

Pushing to other servers

With Mercurial, I found it very convenient to push commits from my laptop to other machines like saturn. Unfortunately, this is more fragile in Git, even discouraged. Some say push only to "bare" git repos, which are created with git init --bare and have no working files, just the contents of the normal .git folder. In that case, push local changes to bitbucket or some other bare repo, then on saturn, pull from that bitbucket or bare repo.

However, by switching branches on the remote repo, you can push to a (working, non-bare) remote repo.

On local system, one time setup:

> git remote add saturn saturn.icl.utk.edu:repos/workflow-dev
> git remote -v
bitbucket	[email protected]:mgates3/workflow.git (fetch)
bitbucket	[email protected]:mgates3/workflow.git (push)
dev	        [email protected]:mgates3/workflow-dev.git (fetch)
dev	        [email protected]:mgates3/workflow-dev.git (push)
saturn	    saturn.icl.utk.edu:repos/workflow-dev (fetch)
saturn	    saturn.icl.utk.edu:repos/workflow-dev (push)

On remote system (e.g., saturn), switch branches:

saturn> git checkout master     # some branch other than my-branch
or
saturn> git switch -c tmp       # or create and checkout tmp branch

On local system, push changes:

> git checkout my-branch
> git push saturn my-branch

Back on saturn, checkout changes:

saturn> git checkout my-branch

If you try to push my-branch when saturn has my-branch checked out, git will refuse (by default).

Clone this wiki locally