In Typescript, the exclamtion is sometimes used in last of the characters.
For example,
const people = { name: 'kim' };
if (people.name!) {
...
}
what is meaning?
In TypeScript, the exclamation mark (!) is used to assert that a value is not null or undefined. It is called the non-null assertion operator. However, it is typically used when you are certain that the value will not be null or undefined, and it should be used with caution.
class변수에는 총 세가지가 있다 1. static 변수 2. instance 변수 3. local 변수
각각의 특성에 대해 알아보고 언제 어떻게 선언하는게 프로그램에 도움이 되는지 고민해보자.
1. static변수
class HumanLifeCycle {
static morningRoutine = 'coffee';
...
}
위에서 보이는 static키워드를 이용한 선언 방식이 static변수이다. 변수를 static으로 선언하게 되면 해당 클래스러 몇개의 인스턴스를 생성하던지 상관없이 메모리상 하나의 변수만 참조하게된다. 따라서, 메모리를 절약할수 있다는 장점이 있다. 그렇다고 늘 static으로 선언해서는 안된다. 단 하나의 변수라는 개념인 만큼 여러 인스턴스에서 하나의 값을 공유하게 되기 때문에 각 인스턴스가 독립적으로 변수를 다뤄야 한다면 static을 써서는 안된다. static이란 이름에 걸맞게 정적이고(변동이 없고) 클래스 인스턴스 간 글로벌하게 쓰이는 경우에 static변수를 쓰도록 하면 어떨까?
위 코드에서 보이는 this.pattern이 instance변수에 해당한다. new연산자를 통해 인스턴스를 생성할때 마다 메모리에 해당 변수에 대한 공간을 할당한다. 즉, 인스턴스간 독립적인 변수를 갖게된다. static이 클래스 레벨에서 변수를 공유하는것과 달리 인스턴스 레벨에서 변수를 다룬다는 차이가 있다. 메모리에 예민한 프로그램이 아니라면 직관적인 instance변수를 주로 즐겨쓰지만 최고의 프로그래머가 되려면 최적화에 대해 더 고민해볼 필요가 있을것 같다.
3. local변수 클래스 내 메소드에서 선언한 변수에 해당한다. 메소드 내에서 잠시 값을 담아두기 위해 쓰이곤 한다.
분명 많은 개발자들이 any를 쓰는 그 순간까지 아 이거 그런거 없나.. 하고 생각했을텐대 알고보면 바로 그게 제네릭이다 (나만 그랬나..?)
제네릭에대해 바로 알아보자. 제네릭을 정의 하자면 "아직은 정해지지 않았지만 쓰는 사람이 쓰는 타입에 따라 유동성있게 정해지는 타입임"을 의미한다.
일단, 제네릭 없이 각 기계장치에 엔진을 간단 붙여본다.
interface Vehicle {
run() => void;
}
class Car implements Vehicle {
run(runTime: number) {
console.log('4 wheels drive...', runTime);
}
changeWheel(wheelNumber: number) {
if (wheelNumber > 4)
throw new Error('You have only 4 wheels...');
console.log('changed whleel');
}
}
class Airplane implements Vehicle {
run(runTime: number) {
console.log('2 wings drive...', runTime);
}
fixWing(wingPosition: 'left' | 'right') {
console.log('fix..!');
}
}
function doPreRun(vehicle: Vehicle): Vehicle {
vehicle.run(30);
return vehicle;
}
const genesis = new Car();
genesis.changeWheel(3); // ok
doPreRun(genesis);
genesis.changeWheel(4); // failed error
위 코드에서 보다시피 예열이 되어 반환된 인스턴스는 본인의 정체성을 잃고 interface의 구현만 갖는다. doPreRun함수가 Vehicle 인스턴스를 리턴하기 때문이다.
이러한 상황을 피하고 보다 유동성있고 추상화하여 작성하고 싶다? 이럴때 바로 제네릭이 필요하다 제네릭을 이용해 코드를 작성해본다.
function doPreRun<T>(vehicle: T): T {
vehicle.run(30);
return vehicle;
}
const genesis = new Car();
genesis.changeWheel(3); // ok
doPreRun(genesis);
genesis.changeWheel(4); // ok
T라는 캐릭터를 썻는데 어려워할 필요없다. 임의의 T(Type)이라는 것이니까 다른 표현으로 V(Value), K(Key) 등이 많이 쓰인다고한다.
조금만 더 나아가보자. 지금은 T라는 타입이 너무 광번위 하여 다른 개발자가 Vehicle이 아닌 인스턴스를 넘길 위험이 존재해 보인다.
function doPreRun<T extends Vehicle>(vehicle: T): T {
vehicle.run(30);
return vehicle;
}
const genesis = new Car();
genesis.changeWheel(3); // ok
doPreRun(genesis);
genesis.changeWheel(4); // ok
이렇게 Vehicle을 확장(상속)하여 구현한 타입만을 허용하도록 수정할 수 있다.
제네릭을 더욱 유용하게 쓰기위해 constrains라는 것에 대해 알아볼것이다. constrains는 제네릭에 조건을 거는것이다
앱을 구현하기위한 데이터와 함수로 구성됨 정의된 순서대로 함수가 구동하는 프로그래밍 구현을 위해서는 모든 코드를 이해해야하여 유지보수가 어렵고 에러찾기가 어렵다.
객체지향 프로그래밍
서로 관련있는 데이터와 함수를 여러가지 오브젝트(객체)로 구성하여 프로그래밍 해나가는 것. 문제가 생기면 관련된 오브젝트만 확인하면 되고, 기능을 추가한다면 새로운 오브젝트를 만들면 되어 유지보수가 쉬워진다.
오브젝트(객체)
데이터와 함수로 구성됨 하나의 객체로써 명사로 이름을 짓는다. 오브젝트 내 데이터는 fields/properties라고 부른다. 오브젝트 내 함수는 메소드라고 부른다.
클래스와 오브젝트
클래스는 오브젝트를 만드는 일종의 템플릿 템플릿(클래스)를 이용해 만든 객체가 오브젝트 이러한 객체를 클래스의 인스턴스라고 부른다.
객체지향 프로그래밍의 원칙
캡슐화 / 추상화 / 상속 / 다형성 이 4가지를 객체지향 프로그래밍의 4원칙 이라고 부른다.
캡슐화
여러 기능들 중 서로 연관성있는 기능들을 묶는것을 "캡슐화"
eg) 감기약 캡슐에 다양한 성분의 약이 있지만 캡슐로 감싸두고, 안에 무엇이 있는지 모르고 그냥 먹는것과 같음
eg) 우리가 고양이 내부의 상태를 직접 바꿀수 없다. 고양이와 놀아줌으로써 내부 상태를 바꿀수는 있다.
이렇듯 안에 있는 것은 직접 건드리지 않고, 외부에 공개된 기능을 통해 내부의 값을 변경하는 것 그리고 어떤것을 외부에 공개하고 공개 안할지 결정하는것
이러한 일련의 행위를 "캡슐화" 라고함 키워드: public / private / protected
멤버변수의 기본 상태: public 외부에 공개하고 싶지 않을때: private 외부에서는 접근 할수없지만 상속한 자식 클래스에서는 접근할수있게 해주는것: protected
추상화
내부의 복잡한 기능을 다 이해하지 않더라도 외부에서 내부가 어떻게 구현됐는지는 상관않고 외부에서 보이는 인터페이스 만으로 이용할 수 있는 것 다시 말하면, 우리가 자동차의 원리를 몰라도 엑셀과 브레이크만 알면 운전할수 있듯이 외부에서는 내부에 어떻게 구현됐는지 신경쓰지않고 인터페이스만을 통해 함수를 이용할 수 있도록 하는 것
추상화 전
type Animal = 'dog', 'cat', 'pig';
type PetSkill = 'give me hand', 'sit down', 'high five';
class Pet {
private skills: PetSkill;
cosntructor(public kind: Animal, public name: string) {
}
training(skill: PetSkill) {
this.edu();
this.charming();
this.skills.push(skill);
}
charming() {
...
}
edu() {
...
}
}
추상화 방법1 - 정보은닉
type Animal = 'dog', 'cat', 'pig';
type PetSkill = 'give me hand', 'sit down', 'high five';
class Pet {
private skills: PetSkill;
cosntructor(public kind: Animal, public name: string) {
}
training(skill: PetSkill) {
this.edu();
this.charming();
this.skills.push(skill);
}
private charming() {
...
}
private edu() {
...
}
}
const myPuppy = new Pet('dog', 'meongu');
myPuppy.training('give me hand');
추상화 방법2 - interface
type Animal = 'dog', 'cat', 'pig';
type PetSkill = 'give me hand', 'sit down', 'high five';
interface Pet {
training(skill: PetSkill): void;
}
class FriendPet {
private skills: PetSkill;
cosntructor(public kind: Animal, public name: string) {
}
training(skill: PetSkill) {
this.edu();
this.charming();
this.skills.push(skill);
}
charming() {
...
}
edu() {
...
}
}
const myPuppy: Pet= new FriendPet('dog', 'meongu');
myPuppy.training('give me hand');
상속
상속을 이용하면 잘 정의해둔 클래스를 재사용하여 부모 클래스의 function, properties를 상속하여 새로운 클래스를 쉽게 만들어 낼 수있다.
다형성
상속을 받은 자식 클래스들은 자신이 무엇인지와 관계없이 부모 클래스의 function을 공통적으로 호출할 수 있다.
static
모든 인스턴스가 동일하게 가지는 멤버변수가 있다면 static을 붙여주자. 그러면 class레벨로 멤버변수가 변경되어, 매번 인스턴스를 만들때마다 메모리를 차지하지 않는다.
get/set
getter, setter를 활용하면 클래스 사용자는 멤버변수를 다루듯이 age를 쓸수있으면서, 내부적으로는 예외처리까지 할수있다. 이러한 큰 장점이 있어 많은 개발자들이 애용하고있다. (어떤 개발자는 getter/setter가 몇몇 경우에 직관적이지 못하다 등의 이유로 쓰지 않는것을 권장하기도 한다.)
class Person {
private nationalAge: number;
constructor(private firstname: string, public lastname: string) {
}
get fullname(): string {
return `${this.firstname}${this.lastname}`;
}
get age(): number {
return age;
}
set age(num: number) {
if (!num || typeof num !== number) {
throw new Error('숫자를 넘기셔야지요!');
}
this.nationalAge = num;
}
}
function getName(first: string, last?: string): string {
return first + last;
}
getName('lee'); // ok
getName('lee', undefined); // ok
getName('lee', 'webster'); // ok
2. undefined
function getName(first: string, last: string | undefined): string {
return first + last;
}
getName('lee'); // error
getName('lee', undefined); // ok
getName('lee', 'webster'); // ok
// js
const DAYS = Object.freeze({
"MONDAY": 1,
"TUESDAY": 2,
});
//ts - enum에 값을 지정하지 않으면 맨 위부터 0으로 초기화됨
enum DAYS {
Monday,
Tuesday
};
enum REST {
POST = 'POST',
GET = 'GET',
};
enum으로 타입을 설정했더라도
다른 값으로 값을 초기화 할수 있다는 이슈가 있다.
즉 타입이 보장되지 않는다.
const yesterday: DAYS = 1; // ok
const today: DAYS = 10; // ok..?
위와 같은 경우에는
유니온 타입으로 타입을 정의하는게 더 바람직하다.
20. type assertion💩
타입에 대해 완전히 확신할때 타입을 강제로 캐스팅하여
타입의 메소드를 사용할 수 있다.
typescript에서는 거의 쓰이지 않지만
js코드와 병행해서 작업할때 종종 쓰일 수 있다.
function sum(a: number, b, number): number {
return a + b;
}
const result = sum(1, 5);
(result as string).length; // undefined
(result as Array<number>).length // app crahsed
result!.length; // app crahsed, 완전히 타입에 대해 확신할때 !를 사용하여 warning을 제거할 수 있다
# stash에 작업중인 사항 보관하기 - 1
git stash
# stash에 작업중인 사항 보관하기 - 2
git stash push
# msg와 함꼐 stash
git stash -m "<메세지>"
# 작업내역을 유지한채 stash에도 추가 (stash에 현재 작업내용 카피 떠두는 개념)
git stash -m "<메시지>" --keep-index
# untracking 파일포함 보관하기
git stash -u
# stash 목록 보기
git stash list
# stash 이력별 수정내용 보기
git stash show <stashId> -p
# stash 목록에서 최신 stash 다시 가져오기 (stash는 유지됨)
git stash apply
# stash 목록에서 선택해 stash 다시 가져오기
git stash apply <stashId>
# stash 목록에서 최신 stash 다시 가져오기 (stash는 제거됨)
git stash pop
# stash 제거
git stash drop <stashId>
# stash 전체 삭제
git stash clear
# 최신 stash를 가져오면서 새 브랜치에 적용
git stash branch <브랜치명>
git restore
working directory에 있는 작업중인 사항을 초기화
# working directory내 전체 초기화
git restore .
# 새로 추가된 파일은 restore로 제거되지 않음, 이때 사용하는 force directory
git clean -fd
# working directory내 특정 파일 초기화
git restore <파일명>
# add되어 staging area에 있는 파일을 working directory로 다시 가져옴
git restore --staged <파일명>
# add되어 staging area에 있는 모든 파일을 working directory로 다시 가져옴
git restore --staged .
# commit history로 초기화
git restore --source=<해시코드> <초기화할파일>
git commit --amend
commit 메시지를 잘못 적은 경우,
commit한 파일을 수정해야되는 경우,
직전에 수정한 commit을 수정할 수 있다
# 직전 커밋 메시지 수정하기
git commit --amend -m "<메시지>"
# 커밋한 파일의 내용을 수정해야할때, 추가 커밋없이 파일 수정
커밋했던 파일 현재 working directory에서 수정
git commit --amend
git reset
특정한 커밋으로 모든것을 초기화(지움) 할수있다.
원하는 버전의 해시코드로 reset하면 그 사이의 커밋은 제거된다.
--mixed
커밋 히스토리를 삭제하면서 해당 내용을
working directory로 작업 내역이 옮겨짐
--soft
커밋 히스토리를 삭제하면서 해당 내용을
staging area로 작업 내역이 옮겨짐
--hard
커밋 히스토리를 삭제하면서 해당 내용도 완전히 삭제
# 현재 ~ 해당 해시코드 사이의 commit 제거 + 내용은 working directory로 이동 - 1
git reset <해시코드>
# 현재 ~ 해당 해시코드 사이의 commit 제거 + 내용은 working directory로 이동 - 2
git reset --mixed <해시코드>
# 현재 ~ 해당 해시코드 사이의 commit 제거 + 내용은 staging area로 이동
git reset --soft <해시코드>
# 현재 ~ 해당 해시코드 사이의 commit 제거 + 내용은 삭제
git reset --hard <해시코드>
**주의사항
현재 working directory에 수정사항이 있는 상태에서
reset을 진행했다면 (특히, --hard)
작업중이 사항을 전부 잃을 가능성이 매우 높다
reset을 하기전에 working directory에 있는 파일들은
가능한 commit/stash 해두도록 하자.
git reflog
이전에 HEAD가 가리키고 있던 내역들을 전부 확인할 수 있다.
(내가 여태까지 실행했던 명령과 HEAD가 가리켰던 포인터들을 전부 확인 가능)
# 명령 이력 조회
git reflog
# 특정 해시코드로 돌아가기
git reset --hard <해시코드>
git revert
해당하는 커밋의 변경 사항을 완전히 취소할 수 있다.
명령후 revert 된 커밋이 새로 추가된다.
특히, 이미 서버 마스터 브랜치에 커밋인 경우
rebase, reset보다는 revert를 사용하는것이 맞다.
--no-commit을 쓸때, revert 커밋에서는 다른 파일을
수정, 삭제 등을 진행하는일은 절대로 없도록 한다.
# 특정 커밋 완전히 제거 + 취소사항 커밋
git revert <해시코드>
# 특정 커밋 완전히 제거 + 취소사항 staging area로 이동
git revert --no-commit <해시코드>
git rebase -i (interactive)
이전에 커밋된 사항을 수정할 수 있다.
이미 서버에 업로드된 사항은 다루지 말자.
오래된 순으로
5,4,3,2,1 이라는 커밋이 있을때
4라는 커밋을 수정하고 싶다면
4가 가리키는 이전 커밋인 5를 기준으로 해야한다.
rebase하게되면 기준 이후의 커밋들이
전부 수정되는 개념이기 때문이다.
(실제로, rebase 범주내 커밋들은 rebase후 해시코드가 변경됨)
# interactive rebase
git rebase -i <해시코드>
interactive 옵션
pick(p): 이거 써
reward(r): ok, 메시지는 변경
edit(e): ok, 변경사항 변경
squash(s): 여러가지 커밋 묶기 + 새 커밋 메시지
fixup(f): 여러가지 커밋 묶기
drop(d): 해당하는 커밋을 제거
git rebase & reset으로 커밋 분할
하나의 커밋에는 하나의 작업단위만 수행하는게 일반적이다.
예를들면, 버그수정 / 기능추가 / 라이브러리 추가 등.
그래야만 문제가 발생했을때 빠르게 revert해서 대응한다거나
문제의 커밋으로 되돌아가서 수정하는것이 수월하기 때문이다.
근데 나를 포함한 많은 개발자들이 실수하는게 여러개의
작업을 하나의 커밋에 추가하는 것이다.
실수를해야 사람이다.
실수로 여러 작업을 한 커밋을 다시 분할하는 방법에 대해 알아본다.
# A라는 비대한 커밋을 쪼개보자
# 1. A직전 커밋 해시코드 확인
git status
# 2. rebase interactive
git rebase -i <A직전 커밋 해시코드>
# 3. interactive - edit (커밋 수정하기 옵션)
pick -> e
# 4. reset으로 커밋을 working directory로 가져오기
git reset <가져올 해시코드>
# 5. 가져온 커밋에서 작업단위로 쪼개서 commit 하기
git add <특정 작업파일들>
git commit -m "<작업메시지>"
git add <특정 작업파일들>
git commit -m "<작업메시지>"
#6. continue
git rebase --continue
그런가 하면 commit들을 하나의 commit으로 묶는것도 가능하다.
# 1. 작업 범주 해시코드 확인
git status
# 2. rebase interactive
git rebase -i <squash할 범위 +1의 해시코드>
# 3. squash
squash(커밋 묶기)를 진행할때는 가장 최근 commit은 pick으로 유지한채
그외의 묶을 것들의 옵션을 s(squash)로 변경한다.
# 4. 커밋 메시지 작성하기
필요없는 내용은 지우고 커밋 메시지 작성
1. working directory: 작업중인 로컬 환경, untracked/tracked으로 구분됨 2. staging area: git add를 통해 작업 내역이 올라가는 장소 3. git directory: git commit하면 작업 내역이 git directory에 위치됨 4. server(github/gitlab): git push하여 서버에 업로드, git pull하여 내려받음
commit 뜻
1. 스냅샷된 정보를 담음 2. ID로 고유한 hash코드가 부여됨 3. 누가 작성했는지, 언제 했는지가 기록됨
git add
# 1개 파일 add
git add <file>
# 2개 파일 add
git add <file1> <file2>
# 전체 수정 파일 add
git add .
# 전체 수정 파일 add
git add -A
# 디렉토리 내 전체 파일 add
git add *
working directory의 수정된 사항들을 staging area로 옮기는 행위
git rm --cached <file> 명령어로 staging area의 파일을 working directory로 다시 옮기는것도 가능
.gitignore
git에 포함하고 싶지 않은 파일들을 지정 지정된 파일들은 track되지 않아 git 명령의 대상이 아니게됨
git status
git의 상태를 보여줌
# 짧게보기
git status -s
# 풀버전 (기본)
git status
git status --long
git diff
이전 버전과 현재의 수정사항을 비교하여 보여줌 -는 이전 버전을 의미 +는 현재 버전, 수정된 사항을 의미
# 이전 버전과 현재버전을 비교
git diff
# staging area만 확인
git diff --staged
git commit
staging area의 변경 사항들은 git repository로 이동시킴
# 메시지와 함께 commit
git commit -m "some message"
# 전체 수정사항을 add 후, commit
git add .
git commit -m "some message"
# 전체 수정사항 add와 commit을 동시에
git commit -am "some message"
commit이야 말로 개발의 history가 됨 기능별, 의미있는 수정/기능 단위로 commit을 진행 커밋의 메시지는 현재형, 동사로 작성하는게 일반적 (init/add/fix)
git log
간단하게 commit 히스토리를 볼 수 있음 위에 있을수록 최신 commit
# 커밋 히스토리 보기
git log
# 간단하게 보기 (해시번호 + 커밋 메시지)
git log --oneline
# 수정사항 함께보기
git log -p
# 텍스트 GUI로 브랜치 함께 보기
git log --oneline --graph all
git alias
나만의 명령어를 만들 수 있음
git config --global alias.<이름> "<수행할 액션>"
HEAD
각 커밋은 이전 커밋을 가리킨다. HEAD는 내가 현재 위치한 버전을 가리킨다. 즉, 현재 내가 보고있는 커밋을 가리킨다.
HEAD~1은 직전 버전 HEAD~2은 이전이전 버전 HEAD~3은 이전이전이전 버전 을 의미한다.
git tag
특정한 커밋을 북마크해두고 싶을때 쓰는것 제품을 릴리즈할떄 제품의 버전명을 tag해두는게 일반적
# 버전관리 - semantic versioning
major.minor.fix
# 예시
1.0.1
# 태그 추가
git tag <태그명>
# 특정 커밋 해시코드에 태그 추가
git tag <태그명> <해시코드>
# 특정 커밋 해시코드에 태그를 메시지와 함께 추가
git tag <태그명> <해시코드> -am "메시지"
# 태그 내용 조회
git show <해시코드>
# 태그 찾기, 와일드 카드 사용가능
git tag -l "v1.*"
# 태그 삭제
git tag -d <태그명>
git branch
기능개발 / 버그픽스 등 업무를 진행함에 있어 개발자들 간에 병렬적으로 개발하기 위해 각자의 작업 공간을 나누어 개발을 진행한다.
이때 필요한게 나만의 작업공간 "branch" 특정 기능을 위한 작업공간 "branch"
특정 브랜치에서 git commit을 해나가다가 개발이 완료되면 기준 브랜치 혹은 마스터 브랜치로 병합을 하게되는데,(merge)
이때 그냥 병합하게 되면 브랜치가 지저분해져서 커밋 내역을 합쳐서 새로운 하나의 커밋을 만든 다음 병합하는 경우가 많다. (rebase & merge)
# 로컬의 브랜치 확인
git branch
# 로컬 & 서버 브랜치 확인
git branch --all
# 브랜치 이동 방법, 2가지
git switch <브랜치 이름>
git checkout <브랜치 이름>
# 브랜치 생성 & 이동 방법, 2가지
git switch -C <브랜치 이름>
git checkout -b <브랜치 이름>
# 해시코드를 이용해 브랜치 이동
git checkout <해시코드>
# 현재 브랜치에 merge된 브랜치 확인
git branch --merged
# 현재 브랜치에 merge얀된 브랜치 확인
git branch --no-merged
# 브랜치 삭제
git branch -d <브랜치 이름>
# 브랜치 삭제 & 원격저장소에 브랜치 삭제 반영
git branch -d <브랜치 이름>
git push origin --delete <브랜치 이름>
# 브랜치 이름 변경
git branch --move <이전 브랜치 이름> <새 브랜치 이름>
# 브랜치 이름 변경 & 원격 저장소에 변경된 브랜치 반영
git branch --move <이전 브랜치 이름> <새 브랜치 이름>
git push --set-upstream origin <새 브랜치명>
기준점을 재설정 함으로써 fast forward merge가 가능한 상황을 만들어 브랜치를 깔끔하게 관리하기 위한 수단.
마치 하나의 브랜치에서 작업한듯한 히스토리를 만들 수 있다.
** 로컬레포지토리에서는 자유롭게 rebase를 이용해도 된다.
다만, remote 레포지토리에 반영된 사항에 rebase를 하다가 merge conflict이 발생할 수 있다.
# feature/b를 master에 rebase
git switch feature/b
git rebase master
git switch master
git merge feature/b
# feature/view, feature/view/logic
# logic을 먼저 올려줄것을 요청하는 다른 개발자가 있어서
# feature/view/logic을 master에 merge 해보기
git switch master
git rebase --onto master feature/view feature/view/logic
git merge feature/view/logic
rebase의 또 다른 활용법이 있다. 하나의 feature를 구현함에 있어서도 분명 세부적인 기능들이 나눠진다.
따라서, 로컬에서 개발을 진행할 때 n번에 나눠서 commit을 진행하게 되는데 이때 그냥 push하면 모든 commit기록이 remote 서버로 넘어가기 때문에 브랜치가 지저분해진다.
내 로컬 작업을 간단히 정리해서 push하기위해 아래와 같은 사전작업을 할 수 있다.
# git log로 커밋 이력 확인하기
git log
# 최근 3개의 커밋에 대해 정리하기
git rebase -i @~3
# push하기
git push origin <브랜치명>
위 rebase 작업에서는 2번의 텍스트 에디터 화면을 보게된다. 1. commit 선별하기 p (pick, 사용하기) | r (reward, 사용하되 커밋 메시지 수정하기) | s (squash, 사용하되 이전의 커밋에 녹이기) 등의 옵션을 이용할 수 있다. 2. commit 메시지 정리하기 dd명령어를 이용해 원하는 라인을 제외하고 모두 지우거나 새 커밋 메시지를 작성하여 커밋 메시지를 깔끔하게 할 수 있다. (해당 내용은 다음 포스팅에서 더 상세히 다룸)